test_organization_release_details.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. from __future__ import absolute_import
  2. import unittest
  3. from sentry.utils.compat.mock import patch
  4. from datetime import datetime
  5. import pytz
  6. from django.core.urlresolvers import reverse
  7. from sentry.constants import MAX_VERSION_LENGTH
  8. from sentry.models import (
  9. Activity,
  10. Environment,
  11. File,
  12. Release,
  13. ReleaseCommit,
  14. ReleaseFile,
  15. ReleaseProject,
  16. ReleaseProjectEnvironment,
  17. Repository,
  18. )
  19. from sentry.testutils import APITestCase
  20. from sentry.api.endpoints.organization_release_details import OrganizationReleaseSerializer
  21. class ReleaseDetailsTest(APITestCase):
  22. def test_simple(self):
  23. user = self.create_user(is_staff=False, is_superuser=False)
  24. org = self.organization
  25. org.flags.allow_joinleave = False
  26. org.save()
  27. team1 = self.create_team(organization=org)
  28. team2 = self.create_team(organization=org)
  29. project = self.create_project(teams=[team1], organization=org)
  30. project2 = self.create_project(teams=[team2], organization=org)
  31. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  32. release2 = Release.objects.create(organization_id=org.id, version="12345678")
  33. release.add_project(project)
  34. release2.add_project(project2)
  35. environment = Environment.objects.create(organization_id=org.id, name="prod")
  36. environment.add_project(project)
  37. environment.add_project(project2)
  38. self.create_member(teams=[team1], user=user, organization=org)
  39. self.login_as(user=user)
  40. ReleaseProjectEnvironment.objects.create(
  41. project_id=project.id,
  42. release_id=release.id,
  43. environment_id=environment.id,
  44. new_issues_count=5,
  45. )
  46. ReleaseProject.objects.filter(project=project, release=release).update(new_groups=5)
  47. url = reverse(
  48. "sentry-api-0-organization-release-details",
  49. kwargs={"organization_slug": org.slug, "version": release.version},
  50. )
  51. response = self.client.get(url)
  52. assert response.status_code == 200, response.content
  53. assert response.data["version"] == release.version
  54. assert response.data["newGroups"] == 5
  55. # no access
  56. url = reverse(
  57. "sentry-api-0-organization-release-details",
  58. kwargs={"organization_slug": org.slug, "version": release2.version},
  59. )
  60. response = self.client.get(url)
  61. assert response.status_code == 404
  62. def test_multiple_projects(self):
  63. user = self.create_user(is_staff=False, is_superuser=False)
  64. org = self.organization
  65. org.flags.allow_joinleave = False
  66. org.save()
  67. team1 = self.create_team(organization=org)
  68. team2 = self.create_team(organization=org)
  69. project = self.create_project(teams=[team1], organization=org)
  70. project2 = self.create_project(teams=[team2], organization=org)
  71. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  72. release.add_project(project)
  73. release.add_project(project2)
  74. self.create_member(teams=[team1, team2], user=user, organization=org)
  75. self.login_as(user=user)
  76. url = reverse(
  77. "sentry-api-0-organization-release-details",
  78. kwargs={"organization_slug": org.slug, "version": release.version},
  79. )
  80. response = self.client.get(url)
  81. assert response.status_code == 200, response.content
  82. def test_wrong_project(self):
  83. user = self.create_user(is_staff=False, is_superuser=False)
  84. org = self.organization
  85. org.flags.allow_joinleave = False
  86. org.save()
  87. team1 = self.create_team(organization=org)
  88. project = self.create_project(teams=[team1], organization=org)
  89. project2 = self.create_project(teams=[team1], organization=org)
  90. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  91. release.add_project(project)
  92. self.create_member(teams=[team1], user=user, organization=org)
  93. self.login_as(user=user)
  94. url = reverse(
  95. "sentry-api-0-organization-release-details",
  96. kwargs={"organization_slug": org.slug, "version": release.version},
  97. )
  98. response = self.client.get(url, {"project": project2.id})
  99. assert response.status_code == 404
  100. response = self.client.get(url, {"project": project.id})
  101. assert response.status_code == 200
  102. class UpdateReleaseDetailsTest(APITestCase):
  103. @patch("sentry.tasks.commits.fetch_commits")
  104. def test_simple(self, mock_fetch_commits):
  105. user = self.create_user(is_staff=False, is_superuser=False)
  106. org = self.organization
  107. org.flags.allow_joinleave = False
  108. org.save()
  109. repo = Repository.objects.create(
  110. organization_id=org.id, name="example/example", provider="dummy"
  111. )
  112. repo2 = Repository.objects.create(
  113. organization_id=org.id, name="example/example2", provider="dummy"
  114. )
  115. team1 = self.create_team(organization=org)
  116. team2 = self.create_team(organization=org)
  117. project = self.create_project(teams=[team1], organization=org)
  118. project2 = self.create_project(teams=[team2], organization=org)
  119. base_release = Release.objects.create(organization_id=org.id, version="000000000")
  120. base_release.add_project(project)
  121. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  122. release2 = Release.objects.create(organization_id=org.id, version="12345678")
  123. release.add_project(project)
  124. release2.add_project(project2)
  125. self.create_member(teams=[team1], user=user, organization=org)
  126. self.login_as(user=user)
  127. url = reverse(
  128. "sentry-api-0-organization-release-details",
  129. kwargs={"organization_slug": org.slug, "version": base_release.version},
  130. )
  131. self.client.put(
  132. url,
  133. {
  134. "ref": "master",
  135. "headCommits": [
  136. {"currentId": "0" * 40, "repository": repo.name},
  137. {"currentId": "0" * 40, "repository": repo2.name},
  138. ],
  139. },
  140. )
  141. url = reverse(
  142. "sentry-api-0-organization-release-details",
  143. kwargs={"organization_slug": org.slug, "version": release.version},
  144. )
  145. response = self.client.put(
  146. url,
  147. {
  148. "ref": "master",
  149. "refs": [
  150. {"commit": "a" * 40, "repository": repo.name},
  151. {"commit": "b" * 40, "repository": repo2.name},
  152. ],
  153. },
  154. )
  155. mock_fetch_commits.apply_async.assert_called_with(
  156. kwargs={
  157. "release_id": release.id,
  158. "user_id": user.id,
  159. "refs": [
  160. {"commit": "a" * 40, "repository": repo.name},
  161. {"commit": "b" * 40, "repository": repo2.name},
  162. ],
  163. "prev_release_id": base_release.id,
  164. }
  165. )
  166. assert response.status_code == 200, response.content
  167. assert response.data["version"] == release.version
  168. release = Release.objects.get(id=release.id)
  169. assert release.ref == "master"
  170. # no access
  171. url = reverse(
  172. "sentry-api-0-organization-release-details",
  173. kwargs={"organization_slug": org.slug, "version": release2.version},
  174. )
  175. response = self.client.put(url, {"ref": "master"})
  176. assert response.status_code == 404
  177. @patch("sentry.tasks.commits.fetch_commits")
  178. def test_deprecated_head_commits(self, mock_fetch_commits):
  179. user = self.create_user(is_staff=False, is_superuser=False)
  180. org = self.organization
  181. org.flags.allow_joinleave = False
  182. org.save()
  183. repo = Repository.objects.create(
  184. organization_id=org.id, name="example/example", provider="dummy"
  185. )
  186. repo2 = Repository.objects.create(
  187. organization_id=org.id, name="example/example2", provider="dummy"
  188. )
  189. team1 = self.create_team(organization=org)
  190. team2 = self.create_team(organization=org)
  191. project = self.create_project(teams=[team1], organization=org)
  192. project2 = self.create_project(teams=[team2], organization=org)
  193. base_release = Release.objects.create(organization_id=org.id, version="000000000")
  194. base_release.add_project(project)
  195. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  196. release2 = Release.objects.create(organization_id=org.id, version="12345678")
  197. release.add_project(project)
  198. release2.add_project(project2)
  199. self.create_member(teams=[team1], user=user, organization=org)
  200. self.login_as(user=user)
  201. url = reverse(
  202. "sentry-api-0-organization-release-details",
  203. kwargs={"organization_slug": org.slug, "version": base_release.version},
  204. )
  205. self.client.put(
  206. url,
  207. {
  208. "ref": "master",
  209. "headCommits": [
  210. {"currentId": "0" * 40, "repository": repo.name},
  211. {"currentId": "0" * 40, "repository": repo2.name},
  212. ],
  213. },
  214. )
  215. url = reverse(
  216. "sentry-api-0-organization-release-details",
  217. kwargs={"organization_slug": org.slug, "version": release.version},
  218. )
  219. response = self.client.put(
  220. url,
  221. {
  222. "ref": "master",
  223. "headCommits": [
  224. {"currentId": "a" * 40, "repository": repo.name},
  225. {"currentId": "b" * 40, "repository": repo2.name},
  226. ],
  227. },
  228. )
  229. mock_fetch_commits.apply_async.assert_called_with(
  230. kwargs={
  231. "release_id": release.id,
  232. "user_id": user.id,
  233. "refs": [
  234. {"commit": "a" * 40, "previousCommit": None, "repository": repo.name},
  235. {"commit": "b" * 40, "previousCommit": None, "repository": repo2.name},
  236. ],
  237. "prev_release_id": base_release.id,
  238. }
  239. )
  240. assert response.status_code == 200, response.content
  241. assert response.data["version"] == release.version
  242. release = Release.objects.get(id=release.id)
  243. assert release.ref == "master"
  244. # no access
  245. url = reverse(
  246. "sentry-api-0-organization-release-details",
  247. kwargs={"organization_slug": org.slug, "version": release2.version},
  248. )
  249. response = self.client.put(url, {"ref": "master"})
  250. assert response.status_code == 404
  251. def test_commits(self):
  252. user = self.create_user(is_staff=False, is_superuser=False)
  253. org = self.organization
  254. org.flags.allow_joinleave = False
  255. org.save()
  256. team = self.create_team(organization=org)
  257. project = self.create_project(teams=[team], organization=org)
  258. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  259. release.add_project(project)
  260. self.create_member(teams=[team], user=user, organization=org)
  261. self.login_as(user=user)
  262. url = reverse(
  263. "sentry-api-0-organization-release-details",
  264. kwargs={"organization_slug": org.slug, "version": release.version},
  265. )
  266. response = self.client.put(url, data={"commits": [{"id": "a" * 40}, {"id": "b" * 40}]})
  267. assert response.status_code == 200, (response.status_code, response.content)
  268. rc_list = list(
  269. ReleaseCommit.objects.filter(release=release)
  270. .select_related("commit", "commit__author")
  271. .order_by("order")
  272. )
  273. assert len(rc_list) == 2
  274. for rc in rc_list:
  275. assert rc.organization_id == org.id
  276. def test_activity_generation(self):
  277. user = self.create_user(is_staff=False, is_superuser=False)
  278. org = self.organization
  279. org.flags.allow_joinleave = False
  280. org.save()
  281. team = self.create_team(organization=org)
  282. project = self.create_project(teams=[team], organization=org)
  283. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  284. release.add_project(project)
  285. self.create_member(teams=[team], user=user, organization=org)
  286. self.login_as(user=user)
  287. url = reverse(
  288. "sentry-api-0-organization-release-details",
  289. kwargs={"organization_slug": org.slug, "version": release.version},
  290. )
  291. response = self.client.put(url, data={"dateReleased": datetime.utcnow().isoformat() + "Z"})
  292. assert response.status_code == 200, (response.status_code, response.content)
  293. release = Release.objects.get(id=release.id)
  294. assert release.date_released
  295. activity = Activity.objects.filter(
  296. type=Activity.RELEASE, project=project, ident=release.version
  297. )
  298. assert activity.exists()
  299. def test_activity_generation_long_release(self):
  300. user = self.create_user(is_staff=False, is_superuser=False)
  301. org = self.organization
  302. org.flags.allow_joinleave = False
  303. org.save()
  304. team = self.create_team(organization=org)
  305. project = self.create_project(teams=[team], organization=org)
  306. release = Release.objects.create(organization_id=org.id, version="x" * 65)
  307. release.add_project(project)
  308. self.create_member(teams=[team], user=user, organization=org)
  309. self.login_as(user=user)
  310. url = reverse(
  311. "sentry-api-0-organization-release-details",
  312. kwargs={"organization_slug": org.slug, "version": release.version},
  313. )
  314. response = self.client.put(url, data={"dateReleased": datetime.utcnow().isoformat() + "Z"})
  315. assert response.status_code == 200, (response.status_code, response.content)
  316. release = Release.objects.get(id=release.id)
  317. assert release.date_released
  318. activity = Activity.objects.filter(
  319. type=Activity.RELEASE, project=project, ident=release.version[:64]
  320. )
  321. assert activity.exists()
  322. class ReleaseDeleteTest(APITestCase):
  323. def test_simple(self):
  324. user = self.create_user(is_staff=False, is_superuser=False)
  325. org = self.organization
  326. org.flags.allow_joinleave = False
  327. org.save()
  328. team = self.create_team(organization=org)
  329. project = self.create_project(teams=[team], organization=org)
  330. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  331. release.add_project(project)
  332. self.create_member(teams=[team], user=user, organization=org)
  333. self.login_as(user=user)
  334. release_file = ReleaseFile.objects.create(
  335. organization_id=project.organization_id,
  336. release=release,
  337. file=File.objects.create(name="application.js", type="release.file"),
  338. name="http://example.com/application.js",
  339. )
  340. url = reverse(
  341. "sentry-api-0-organization-release-details",
  342. kwargs={"organization_slug": org.slug, "version": release.version},
  343. )
  344. response = self.client.delete(url)
  345. assert response.status_code == 204, response.content
  346. assert not Release.objects.filter(id=release.id).exists()
  347. assert not ReleaseFile.objects.filter(id=release_file.id).exists()
  348. def test_existing_group(self):
  349. user = self.create_user(is_staff=False, is_superuser=False)
  350. org = self.organization
  351. org.flags.allow_joinleave = False
  352. org.save()
  353. team = self.create_team(organization=org)
  354. project = self.create_project(teams=[team], organization=org)
  355. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  356. release.add_project(project)
  357. self.create_group(first_release=release)
  358. self.create_member(teams=[team], user=user, organization=org)
  359. self.login_as(user=user)
  360. url = reverse(
  361. "sentry-api-0-organization-release-details",
  362. kwargs={"organization_slug": org.slug, "version": release.version},
  363. )
  364. response = self.client.delete(url)
  365. assert response.status_code == 400, response.content
  366. assert Release.objects.filter(id=release.id).exists()
  367. def test_bad_repo_name(self):
  368. user = self.create_user(is_staff=False, is_superuser=False)
  369. org = self.create_organization()
  370. org.flags.allow_joinleave = False
  371. org.save()
  372. team = self.create_team(organization=org)
  373. project = self.create_project(name="foo", organization=org, teams=[team])
  374. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  375. release.add_project(project)
  376. self.create_member(teams=[team], user=user, organization=org)
  377. self.login_as(user=user)
  378. url = reverse(
  379. "sentry-api-0-organization-release-details",
  380. kwargs={"organization_slug": org.slug, "version": release.version},
  381. )
  382. response = self.client.put(
  383. url,
  384. data={
  385. "version": "1.2.1",
  386. "projects": [project.slug],
  387. "refs": [{"repository": "not_a_repo", "commit": "a" * 40}],
  388. },
  389. )
  390. assert response.status_code == 400
  391. assert response.data == {"refs": [u"Invalid repository names: not_a_repo"]}
  392. def test_bad_commit_list(self):
  393. user = self.create_user(is_staff=False, is_superuser=False)
  394. org = self.create_organization()
  395. org.flags.allow_joinleave = False
  396. org.save()
  397. team = self.create_team(organization=org)
  398. project = self.create_project(name="foo", organization=org, teams=[team])
  399. Repository.objects.create(organization_id=org.id, name="a_repo")
  400. release = Release.objects.create(organization_id=org.id, version="abcabcabc")
  401. release.add_project(project)
  402. self.create_member(teams=[team], user=user, organization=org)
  403. self.login_as(user=user)
  404. url = reverse(
  405. "sentry-api-0-organization-release-details",
  406. kwargs={"organization_slug": org.slug, "version": release.version},
  407. )
  408. response = self.client.put(
  409. url,
  410. data={
  411. "version": "1.2.1",
  412. "projects": [project.slug],
  413. "commits": [{"repository": "a_repo"}],
  414. },
  415. )
  416. assert response.status_code == 400
  417. assert response.data == {"commits": {"id": ["This field is required."]}}
  418. class ReleaseSerializerTest(unittest.TestCase):
  419. def setUp(self):
  420. super(ReleaseSerializerTest, self).setUp()
  421. self.repo_name = "repo/name"
  422. self.repo2_name = "repo2/name"
  423. self.commits = [{"id": "a" * 40}, {"id": "b" * 40}]
  424. self.ref = "master"
  425. self.url = "https://example.com"
  426. self.dateReleased = "1000-10-10T06:06"
  427. self.headCommits = [
  428. {"currentId": "0" * 40, "repository": self.repo_name},
  429. {"currentId": "0" * 40, "repository": self.repo2_name},
  430. ]
  431. self.refs = [
  432. {"commit": "a" * 40, "previousCommit": "", "repository": self.repo_name},
  433. {"commit": "b" * 40, "previousCommit": "", "repository": self.repo2_name},
  434. ]
  435. def test_simple(self):
  436. serializer = OrganizationReleaseSerializer(
  437. data={
  438. "ref": self.ref,
  439. "url": self.url,
  440. "dateReleased": self.dateReleased,
  441. "commits": self.commits,
  442. "headCommits": self.headCommits,
  443. "refs": self.refs,
  444. }
  445. )
  446. assert serializer.is_valid()
  447. assert sorted(serializer.fields.keys()) == sorted(
  448. ["ref", "url", "dateReleased", "commits", "headCommits", "refs"]
  449. )
  450. result = serializer.validated_data
  451. assert result["ref"] == self.ref
  452. assert result["url"] == self.url
  453. assert result["dateReleased"] == datetime(1000, 10, 10, 6, 6, tzinfo=pytz.UTC)
  454. assert result["commits"] == self.commits
  455. assert result["headCommits"] == self.headCommits
  456. assert result["refs"] == self.refs
  457. def test_fields_not_required(self):
  458. serializer = OrganizationReleaseSerializer(data={})
  459. assert serializer.is_valid()
  460. def test_do_not_allow_null_commits(self):
  461. serializer = OrganizationReleaseSerializer(data={"commits": None})
  462. assert not serializer.is_valid()
  463. def test_do_not_allow_null_head_commits(self):
  464. serializer = OrganizationReleaseSerializer(data={"headCommits": None})
  465. assert not serializer.is_valid()
  466. def test_do_not_allow_null_refs(self):
  467. serializer = OrganizationReleaseSerializer(data={"refs": None})
  468. assert not serializer.is_valid()
  469. def test_ref_limited_by_max_version_length(self):
  470. serializer = OrganizationReleaseSerializer(data={"ref": "a" * MAX_VERSION_LENGTH})
  471. assert serializer.is_valid()
  472. serializer = OrganizationReleaseSerializer(data={"ref": "a" * (MAX_VERSION_LENGTH + 1)})
  473. assert not serializer.is_valid()
  474. def test_author_email_patch(self):
  475. serializer = OrganizationReleaseSerializer(
  476. data={"commits": [{"id": "a", "author_email": "email[test]@example.org"}]}
  477. )
  478. assert serializer.is_valid()