test_organization_releases.py 94 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460
  1. import unittest
  2. from datetime import datetime, timedelta
  3. from functools import cached_property
  4. from unittest.mock import patch
  5. import pytz
  6. from django.urls import reverse
  7. from django.utils import timezone
  8. from sentry.api.endpoints.organization_releases import ReleaseSerializerWithProjects
  9. from sentry.api.serializers.rest_framework.release import ReleaseHeadCommitSerializer
  10. from sentry.auth import access
  11. from sentry.constants import BAD_RELEASE_CHARS, MAX_COMMIT_LENGTH, MAX_VERSION_LENGTH
  12. from sentry.locks import locks
  13. from sentry.models import (
  14. Activity,
  15. ApiKey,
  16. ApiToken,
  17. Commit,
  18. CommitAuthor,
  19. Environment,
  20. Release,
  21. ReleaseCommit,
  22. ReleaseHeadCommit,
  23. ReleaseProject,
  24. ReleaseProjectEnvironment,
  25. ReleaseStages,
  26. Repository,
  27. )
  28. from sentry.models.commitfilechange import CommitFileChange
  29. from sentry.models.orgauthtoken import OrgAuthToken
  30. from sentry.plugins.providers.dummy.repository import DummyRepositoryProvider
  31. from sentry.search.events.constants import (
  32. RELEASE_ALIAS,
  33. RELEASE_STAGE_ALIAS,
  34. SEMVER_ALIAS,
  35. SEMVER_BUILD_ALIAS,
  36. SEMVER_PACKAGE_ALIAS,
  37. )
  38. from sentry.silo import SiloMode
  39. from sentry.testutils import (
  40. APITestCase,
  41. ReleaseCommitPatchTest,
  42. SetRefsTestCase,
  43. SnubaTestCase,
  44. TestCase,
  45. )
  46. from sentry.testutils.outbox import outbox_runner
  47. from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
  48. from sentry.types.activity import ActivityType
  49. from sentry.utils.security.orgauthtoken_token import generate_token, hash_token
  50. @region_silo_test(stable=True)
  51. class OrganizationReleaseListTest(APITestCase, SnubaTestCase):
  52. endpoint = "sentry-api-0-organization-releases"
  53. def assert_expected_versions(self, response, expected):
  54. assert [item["version"] for item in response.data] == [e.version for e in expected]
  55. def test_simple(self):
  56. user = self.create_user(is_staff=False, is_superuser=False)
  57. org = self.organization
  58. org2 = self.create_organization()
  59. org.flags.allow_joinleave = False
  60. org.save()
  61. team1 = self.create_team(organization=org)
  62. team2 = self.create_team(organization=org)
  63. project1 = self.create_project(teams=[team1], organization=org)
  64. project2 = self.create_project(teams=[team2], organization=org2)
  65. project3 = self.create_project(teams=[team1], organization=org)
  66. self.create_member(teams=[team1], user=user, organization=org)
  67. self.login_as(user=user)
  68. release1 = Release.objects.create(
  69. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  70. )
  71. release1.add_project(project1)
  72. release2 = Release.objects.create(
  73. organization_id=org2.id, version="2", date_added=datetime(2013, 8, 14, 3, 8, 24, 880386)
  74. )
  75. release2.add_project(project2)
  76. release3 = Release.objects.create(
  77. organization_id=org.id,
  78. version="3",
  79. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386),
  80. date_released=datetime(2013, 8, 15, 3, 8, 24, 880386),
  81. )
  82. release3.add_project(project3)
  83. release4 = Release.objects.create(
  84. organization_id=org.id, version="4", date_added=datetime(2013, 8, 14, 3, 8, 24, 880386)
  85. )
  86. release4.add_project(project3)
  87. response = self.get_success_response(org.slug)
  88. self.assert_expected_versions(response, [release4, release1, release3])
  89. def test_release_list_order_by_date_added(self):
  90. """
  91. Test that ensures that by relying on the default date sorting, releases
  92. will only be sorted according to `Release.date_added`, and
  93. `Release.date_released` should have no effect whatsoever on that order
  94. """
  95. user = self.create_user(is_staff=False, is_superuser=False)
  96. org = self.organization
  97. org.flags.allow_joinleave = False
  98. org.save()
  99. team = self.create_team(organization=org)
  100. project = self.create_project(teams=[team], organization=org)
  101. self.create_member(teams=[team], user=user, organization=org)
  102. self.login_as(user=user)
  103. release6 = Release.objects.create(
  104. organization_id=org.id,
  105. version="6",
  106. date_added=datetime(2013, 8, 10, 3, 8, 24, 880386),
  107. date_released=datetime(2013, 8, 20, 3, 8, 24, 880386),
  108. )
  109. release6.add_project(project)
  110. release7 = Release.objects.create(
  111. organization_id=org.id,
  112. version="7",
  113. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386),
  114. date_released=datetime(2013, 8, 18, 3, 8, 24, 880386),
  115. )
  116. release7.add_project(project)
  117. release8 = Release.objects.create(
  118. organization_id=org.id,
  119. version="8",
  120. date_added=datetime(2013, 8, 14, 3, 8, 24, 880386),
  121. date_released=datetime(2013, 8, 16, 3, 8, 24, 880386),
  122. )
  123. release8.add_project(project)
  124. response = self.get_success_response(org.slug)
  125. self.assert_expected_versions(response, [release8, release7, release6])
  126. def test_release_list_order_by_sessions_empty(self):
  127. self.login_as(user=self.user)
  128. release_1 = self.create_release(version="1")
  129. release_2 = self.create_release(version="2")
  130. release_3 = self.create_release(version="3")
  131. release_4 = self.create_release(version="4")
  132. release_5 = self.create_release(version="5")
  133. # Make sure ordering works fine when we have no session data at all
  134. response = self.get_success_response(self.organization.slug, sort="sessions", flatten="1")
  135. self.assert_expected_versions(
  136. response, [release_5, release_4, release_3, release_2, release_1]
  137. )
  138. def test_release_list_order_by_sessions(self):
  139. self.login_as(user=self.user)
  140. release_1 = self.create_release(version="1")
  141. self.store_session(self.build_session(release=release_1))
  142. release_2 = self.create_release(version="2")
  143. release_3 = self.create_release(version="3")
  144. release_4 = self.create_release(version="4")
  145. release_5 = self.create_release(version="5")
  146. self.bulk_store_sessions([self.build_session(release=release_5) for _ in range(2)])
  147. response = self.get_success_response(self.organization.slug, sort="sessions", flatten="1")
  148. self.assert_expected_versions(
  149. response, [release_5, release_1, release_4, release_3, release_2]
  150. )
  151. response = self.get_success_response(
  152. self.organization.slug, sort="sessions", flatten="1", per_page=1
  153. )
  154. self.assert_expected_versions(response, [release_5])
  155. response = self.get_success_response(
  156. self.organization.slug,
  157. sort="sessions",
  158. flatten="1",
  159. per_page=1,
  160. cursor=self.get_cursor_headers(response)[1],
  161. )
  162. self.assert_expected_versions(response, [release_1])
  163. response = self.get_success_response(
  164. self.organization.slug,
  165. sort="sessions",
  166. flatten="1",
  167. per_page=1,
  168. cursor=self.get_cursor_headers(response)[1],
  169. )
  170. self.assert_expected_versions(response, [release_4])
  171. response = self.get_success_response(
  172. self.organization.slug,
  173. sort="sessions",
  174. flatten="1",
  175. per_page=1,
  176. cursor=self.get_cursor_headers(response)[1],
  177. )
  178. self.assert_expected_versions(response, [release_3])
  179. response = self.get_success_response(
  180. self.organization.slug,
  181. sort="sessions",
  182. flatten="1",
  183. per_page=1,
  184. cursor=self.get_cursor_headers(response)[1],
  185. )
  186. self.assert_expected_versions(response, [release_2])
  187. response = self.get_success_response(
  188. self.organization.slug, sort="sessions", flatten="1", per_page=3
  189. )
  190. self.assert_expected_versions(response, [release_5, release_1, release_4])
  191. response = self.get_success_response(
  192. self.organization.slug,
  193. sort="sessions",
  194. flatten="1",
  195. per_page=3,
  196. cursor=self.get_cursor_headers(response)[1],
  197. )
  198. self.assert_expected_versions(response, [release_3, release_2])
  199. def test_release_list_order_by_build_number(self):
  200. self.login_as(user=self.user)
  201. release_1 = self.create_release(version="test@1.2+1000")
  202. release_2 = self.create_release(version="test@1.2+1")
  203. release_3 = self.create_release(version="test@1.2+200")
  204. self.create_release(version="test@1.2")
  205. self.create_release(version="test@1.2+500alpha")
  206. response = self.get_success_response(self.organization.slug, sort="build")
  207. self.assert_expected_versions(response, [release_1, release_3, release_2])
  208. def test_release_list_order_by_semver(self):
  209. self.login_as(user=self.user)
  210. release_1 = self.create_release(version="test@2.2")
  211. release_2 = self.create_release(version="test@10.0+122")
  212. release_3 = self.create_release(version="test@2.2-alpha")
  213. release_4 = self.create_release(version="test@2.2.3")
  214. release_5 = self.create_release(version="test@2.20.3")
  215. release_6 = self.create_release(version="test@2.20.3.3")
  216. release_7 = self.create_release(version="test@10.0+123")
  217. release_8 = self.create_release(version="test@some_thing")
  218. release_9 = self.create_release(version="random_junk")
  219. response = self.get_success_response(self.organization.slug, sort="semver")
  220. self.assert_expected_versions(
  221. response,
  222. [
  223. release_7,
  224. release_2,
  225. release_6,
  226. release_5,
  227. release_4,
  228. release_1,
  229. release_3,
  230. release_9,
  231. release_8,
  232. ],
  233. )
  234. def test_query_filter(self):
  235. user = self.create_user(is_staff=False, is_superuser=False)
  236. org = self.organization
  237. org.flags.allow_joinleave = False
  238. org.save()
  239. team = self.create_team(organization=org)
  240. project = self.create_project(teams=[team], organization=org)
  241. self.create_member(teams=[team], user=user, organization=org)
  242. self.login_as(user=user)
  243. release = Release.objects.create(
  244. organization_id=org.id,
  245. version="foobar",
  246. date_added=datetime(2013, 8, 13, 3, 8, 24, 880386),
  247. )
  248. release.add_project(project)
  249. release2 = Release.objects.create(
  250. organization_id=org.id,
  251. version="sdfsdfsdf",
  252. date_added=datetime(2013, 8, 13, 3, 8, 24, 880386),
  253. )
  254. release2.add_project(project)
  255. response = self.get_success_response(org.slug, query="oob")
  256. self.assert_expected_versions(response, [release])
  257. response = self.get_success_response(org.slug, query="baz")
  258. self.assert_expected_versions(response, [])
  259. def test_release_filter(self):
  260. user = self.create_user(is_staff=False, is_superuser=False)
  261. org = self.organization
  262. org.flags.allow_joinleave = False
  263. org.save()
  264. team = self.create_team(organization=org)
  265. project = self.create_project(teams=[team], organization=org)
  266. self.create_member(teams=[team], user=user, organization=org)
  267. self.login_as(user=user)
  268. release = Release.objects.create(
  269. organization_id=org.id,
  270. version="foobar",
  271. date_added=datetime(2013, 8, 13, 3, 8, 24, 880386),
  272. )
  273. release.add_project(project)
  274. release2 = Release.objects.create(
  275. organization_id=org.id,
  276. version="sdfsdfsdf",
  277. date_added=datetime(2013, 8, 13, 3, 8, 24, 880386),
  278. )
  279. release2.add_project(project)
  280. response = self.get_success_response(
  281. self.organization.slug, query=f"{RELEASE_ALIAS}:foobar"
  282. )
  283. self.assert_expected_versions(response, [release])
  284. response = self.get_success_response(self.organization.slug, query=f"{RELEASE_ALIAS}:foo*")
  285. self.assert_expected_versions(response, [release])
  286. response = self.get_success_response(self.organization.slug, query=f"{RELEASE_ALIAS}:baz")
  287. self.assert_expected_versions(response, [])
  288. # NOT release
  289. response = self.get_success_response(
  290. self.organization.slug, query=f"!{RELEASE_ALIAS}:foobar"
  291. )
  292. self.assert_expected_versions(response, [release2])
  293. def test_query_filter_suffix(self):
  294. user = self.create_user(is_staff=False, is_superuser=False)
  295. org = self.organization
  296. org.flags.allow_joinleave = False
  297. org.save()
  298. team = self.create_team(organization=org)
  299. project = self.create_project(teams=[team], organization=org)
  300. self.create_member(teams=[team], user=user, organization=org)
  301. self.login_as(user=user)
  302. release = Release.objects.create(
  303. organization_id=org.id,
  304. version="com.foo.BarApp@1.0+1234",
  305. date_added=datetime(2013, 8, 13, 3, 8, 24, 880386),
  306. )
  307. release.add_project(project)
  308. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  309. response = self.client.get(url + "?query=1.0+(1234)", format="json")
  310. assert response.status_code == 200, response.content
  311. assert len(response.data) == 1
  312. assert response.data[0]["version"] == release.version
  313. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  314. response = self.client.get(url + "?query=1.0%2B1234", format="json")
  315. assert response.status_code == 200, response.content
  316. assert len(response.data) == 1
  317. assert response.data[0]["version"] == release.version
  318. def test_semver_filter(self):
  319. self.login_as(user=self.user)
  320. release_1 = self.create_release(version="test@1.2.4+124")
  321. release_2 = self.create_release(version="test@1.2.3+123")
  322. release_3 = self.create_release(version="test2@1.2.5+125")
  323. release_4 = self.create_release(version="some.release")
  324. response = self.get_success_response(self.organization.slug, query=f"{SEMVER_ALIAS}:>1.2.3")
  325. self.assert_expected_versions(response, [release_3, release_1])
  326. response = self.get_success_response(
  327. self.organization.slug, query=f"{SEMVER_ALIAS}:>=1.2.3"
  328. )
  329. self.assert_expected_versions(response, [release_3, release_2, release_1])
  330. response = self.get_success_response(self.organization.slug, query=f"{SEMVER_ALIAS}:1.2.*")
  331. self.assert_expected_versions(response, [release_3, release_2, release_1])
  332. # NOT semver version
  333. response = self.get_success_response(self.organization.slug, query=f"!{SEMVER_ALIAS}:1.2.3")
  334. self.assert_expected_versions(response, [release_4, release_3, release_1])
  335. response = self.get_success_response(
  336. self.organization.slug, query=f"{SEMVER_ALIAS}:>=1.2.3", sort="semver"
  337. )
  338. self.assert_expected_versions(response, [release_3, release_1, release_2])
  339. response = self.get_success_response(self.organization.slug, query=f"{SEMVER_ALIAS}:2.2.1")
  340. self.assert_expected_versions(response, [])
  341. response = self.get_success_response(
  342. self.organization.slug, query=f"{SEMVER_PACKAGE_ALIAS}:test2"
  343. )
  344. self.assert_expected_versions(response, [release_3])
  345. response = self.get_success_response(
  346. self.organization.slug, query=f"{SEMVER_PACKAGE_ALIAS}:test"
  347. )
  348. self.assert_expected_versions(response, [release_2, release_1])
  349. # NOT semver package
  350. response = self.get_success_response(
  351. self.organization.slug, query=f"!{SEMVER_PACKAGE_ALIAS}:test2"
  352. )
  353. self.assert_expected_versions(response, [release_4, release_2, release_1])
  354. response = self.get_success_response(
  355. self.organization.slug, query=f"{SEMVER_BUILD_ALIAS}:>124"
  356. )
  357. self.assert_expected_versions(response, [release_3])
  358. response = self.get_success_response(
  359. self.organization.slug, query=f"{SEMVER_BUILD_ALIAS}:<125"
  360. )
  361. self.assert_expected_versions(response, [release_2, release_1])
  362. # NOT semver build
  363. response = self.get_success_response(
  364. self.organization.slug, query=f"!{SEMVER_BUILD_ALIAS}:125"
  365. )
  366. self.assert_expected_versions(response, [release_4, release_2, release_1])
  367. def test_release_stage_filter(self):
  368. self.login_as(user=self.user)
  369. response = self.get_success_response(
  370. self.organization.slug,
  371. query=f"{RELEASE_STAGE_ALIAS}:adopted",
  372. environment=self.environment.name,
  373. )
  374. assert [r["version"] for r in response.data] == []
  375. replaced_release = self.create_release(version="replaced_release")
  376. adopted_release = self.create_release(version="adopted_release")
  377. not_adopted_release = self.create_release(version="not_adopted_release")
  378. adopted_rpe = ReleaseProjectEnvironment.objects.create(
  379. project_id=self.project.id,
  380. release_id=adopted_release.id,
  381. environment_id=self.environment.id,
  382. adopted=timezone.now(),
  383. )
  384. ReleaseProjectEnvironment.objects.create(
  385. project_id=self.project.id,
  386. release_id=replaced_release.id,
  387. environment_id=self.environment.id,
  388. adopted=timezone.now() - timedelta(minutes=5),
  389. unadopted=timezone.now(),
  390. )
  391. ReleaseProjectEnvironment.objects.create(
  392. project_id=self.project.id,
  393. release_id=not_adopted_release.id,
  394. environment_id=self.environment.id,
  395. )
  396. response = self.get_success_response(
  397. self.organization.slug,
  398. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.ADOPTED}",
  399. environment=self.environment.name,
  400. )
  401. self.assert_expected_versions(response, [adopted_release])
  402. response = self.get_success_response(
  403. self.organization.slug,
  404. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.LOW_ADOPTION}",
  405. environment=self.environment.name,
  406. )
  407. self.assert_expected_versions(response, [not_adopted_release])
  408. response = self.get_success_response(
  409. self.organization.slug,
  410. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.REPLACED}",
  411. environment=self.environment.name,
  412. )
  413. self.assert_expected_versions(response, [replaced_release])
  414. # NOT release stage
  415. response = self.get_success_response(
  416. self.organization.slug,
  417. query=f"!{RELEASE_STAGE_ALIAS}:{ReleaseStages.REPLACED}",
  418. environment=self.environment.name,
  419. )
  420. self.assert_expected_versions(response, [not_adopted_release, adopted_release])
  421. response = self.get_success_response(
  422. self.organization.slug,
  423. query=f"{RELEASE_STAGE_ALIAS}:[{ReleaseStages.ADOPTED},{ReleaseStages.REPLACED}]",
  424. environment=self.environment.name,
  425. )
  426. self.assert_expected_versions(response, [adopted_release, replaced_release])
  427. response = self.get_success_response(
  428. self.organization.slug,
  429. query=f"{RELEASE_STAGE_ALIAS}:[{ReleaseStages.LOW_ADOPTION}]",
  430. environment=self.environment.name,
  431. )
  432. self.assert_expected_versions(response, [not_adopted_release])
  433. response = self.get_success_response(
  434. self.organization.slug,
  435. sort="adoption",
  436. )
  437. self.assert_expected_versions(
  438. response, [adopted_release, replaced_release, not_adopted_release]
  439. )
  440. adopted_rpe.update(adopted=timezone.now() - timedelta(minutes=15))
  441. # Replaced should come first now.
  442. response = self.get_success_response(
  443. self.organization.slug,
  444. sort="adoption",
  445. )
  446. self.assert_expected_versions(
  447. response, [replaced_release, adopted_release, not_adopted_release]
  448. )
  449. response = self.get_success_response(self.organization.slug, sort="adoption", per_page=1)
  450. self.assert_expected_versions(response, [replaced_release])
  451. next_cursor = self.get_cursor_headers(response)[1]
  452. response = self.get_success_response(
  453. self.organization.slug,
  454. sort="adoption",
  455. per_page=1,
  456. cursor=next_cursor,
  457. )
  458. self.assert_expected_versions(response, [adopted_release])
  459. next_cursor = self.get_cursor_headers(response)[1]
  460. response = self.get_success_response(
  461. self.organization.slug,
  462. sort="adoption",
  463. per_page=1,
  464. cursor=next_cursor,
  465. )
  466. prev_cursor = self.get_cursor_headers(response)[0]
  467. self.assert_expected_versions(response, [not_adopted_release])
  468. response = self.get_success_response(
  469. self.organization.slug,
  470. sort="adoption",
  471. per_page=1,
  472. cursor=prev_cursor,
  473. )
  474. prev_cursor = self.get_cursor_headers(response)[0]
  475. self.assert_expected_versions(response, [adopted_release])
  476. response = self.get_success_response(
  477. self.organization.slug,
  478. sort="adoption",
  479. per_page=1,
  480. cursor=prev_cursor,
  481. )
  482. prev_cursor = self.get_cursor_headers(response)[0]
  483. self.assert_expected_versions(response, [replaced_release])
  484. adopted_rpe.update(adopted=timezone.now() - timedelta(minutes=15))
  485. response = self.get_success_response(
  486. self.organization.slug,
  487. query=f"{RELEASE_STAGE_ALIAS}:[{ReleaseStages.LOW_ADOPTION},{ReleaseStages.REPLACED}]",
  488. sort="adoption",
  489. environment=self.environment.name,
  490. )
  491. self.assert_expected_versions(response, [replaced_release, not_adopted_release])
  492. response = self.get_response(
  493. self.organization.slug,
  494. query=f"{RELEASE_STAGE_ALIAS}:invalid_stage",
  495. environment=self.environment.name,
  496. )
  497. assert response.status_code == 400
  498. response = self.get_response(
  499. self.organization.slug,
  500. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.ADOPTED}",
  501. # No environment
  502. )
  503. assert response.status_code == 400
  504. def test_project_permissions(self):
  505. user = self.create_user(is_staff=False, is_superuser=False)
  506. org = self.create_organization()
  507. org.flags.allow_joinleave = False
  508. org.save()
  509. team1 = self.create_team(organization=org)
  510. team2 = self.create_team(organization=org)
  511. project1 = self.create_project(teams=[team1], organization=org)
  512. project2 = self.create_project(teams=[team2], organization=org)
  513. self.create_member(teams=[team1], user=user, organization=org)
  514. self.login_as(user=user)
  515. release1 = Release.objects.create(
  516. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  517. )
  518. release1.add_project(project1)
  519. release2 = Release.objects.create(
  520. organization_id=org.id, version="2", date_added=datetime(2013, 8, 14, 3, 8, 24, 880386)
  521. )
  522. release2.add_project(project2)
  523. release3 = Release.objects.create(
  524. organization_id=org.id,
  525. version="3",
  526. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386),
  527. date_released=datetime(2013, 8, 15, 3, 8, 24, 880386),
  528. )
  529. release3.add_project(project1)
  530. ax = access.from_user(user, org)
  531. assert ax.has_projects_access([project1])
  532. assert ax.has_project_membership(project1)
  533. assert not ax.has_project_membership(project2)
  534. response = self.get_success_response(org.slug)
  535. self.assert_expected_versions(response, [release1, release3])
  536. def test_project_permissions_open_access(self):
  537. user = self.create_user(is_staff=False, is_superuser=False)
  538. org = self.create_organization()
  539. org.flags.allow_joinleave = True
  540. org.save()
  541. team1 = self.create_team(organization=org)
  542. team2 = self.create_team(organization=org)
  543. project1 = self.create_project(teams=[team1], organization=org)
  544. project2 = self.create_project(teams=[team2], organization=org)
  545. self.create_member(teams=[team1], user=user, organization=org)
  546. self.login_as(user=user)
  547. release1 = Release.objects.create(
  548. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  549. )
  550. release1.add_project(project1)
  551. release2 = Release.objects.create(
  552. organization_id=org.id, version="2", date_added=datetime(2013, 8, 14, 3, 8, 24, 880386)
  553. )
  554. release2.add_project(project2)
  555. release3 = Release.objects.create(
  556. organization_id=org.id,
  557. version="3",
  558. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386),
  559. date_released=datetime(2013, 8, 15, 3, 8, 24, 880386),
  560. )
  561. release3.add_project(project1)
  562. ax = access.from_user(user, org)
  563. assert ax.has_projects_access([project1, project2])
  564. assert ax.has_project_membership(project1)
  565. assert not ax.has_project_membership(project2)
  566. response = self.get_success_response(org.slug)
  567. self.assert_expected_versions(response, [release1, release3])
  568. def test_all_projects_parameter(self):
  569. user = self.create_user(is_staff=False, is_superuser=False)
  570. org = self.create_organization()
  571. org.flags.allow_joinleave = True
  572. org.save()
  573. team1 = self.create_team(organization=org)
  574. team2 = self.create_team(organization=org)
  575. project1 = self.create_project(teams=[team1], organization=org)
  576. project2 = self.create_project(teams=[team2], organization=org)
  577. self.create_member(teams=[team1], user=user, organization=org)
  578. self.login_as(user=user)
  579. release1 = Release.objects.create(
  580. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  581. )
  582. release1.add_project(project1)
  583. release2 = Release.objects.create(
  584. organization_id=org.id, version="2", date_added=datetime(2013, 8, 14, 3, 8, 24, 880386)
  585. )
  586. release2.add_project(project2)
  587. response = self.get_success_response(org.slug, project=[-1])
  588. self.assert_expected_versions(response, [release2, release1])
  589. def test_new_org(self):
  590. user = self.create_user(is_staff=False, is_superuser=False)
  591. org = self.organization
  592. team = self.create_team(organization=org)
  593. self.create_member(teams=[team], user=user, organization=org)
  594. self.login_as(user=user)
  595. response = self.get_success_response(org.slug)
  596. self.assert_expected_versions(response, [])
  597. def test_archive_release(self):
  598. self.login_as(user=self.user)
  599. url = reverse(
  600. "sentry-api-0-organization-releases",
  601. kwargs={"organization_slug": self.organization.slug},
  602. )
  603. # test legacy status value of None (=open)
  604. self.release.status = None
  605. self.release.save()
  606. response = self.client.get(url, format="json")
  607. assert response.status_code == 200, response.content
  608. assert len(response.data) == 1
  609. (release_data,) = response.data
  610. response = self.client.post(
  611. url,
  612. format="json",
  613. data={
  614. "version": release_data["version"],
  615. "projects": [x["slug"] for x in release_data["projects"]],
  616. "status": "archived",
  617. },
  618. )
  619. assert response.status_code == 208, response.content
  620. response = self.client.get(url, format="json")
  621. assert response.status_code == 200, response.content
  622. assert len(response.data) == 0
  623. response = self.client.get(url + "?status=archived", format="json")
  624. assert response.status_code == 200, response.content
  625. assert len(response.data) == 1
  626. response = self.client.get(url + "?status=", format="json")
  627. assert response.status_code == 200, response.content
  628. assert len(response.data) == 1
  629. @region_silo_test(stable=True)
  630. class OrganizationReleasesStatsTest(APITestCase):
  631. endpoint = "sentry-api-0-organization-releases-stats"
  632. def setUp(self):
  633. self.project1 = self.create_project(teams=[self.team], organization=self.organization)
  634. self.project2 = self.create_project(teams=[self.team], organization=self.organization)
  635. self.project3 = self.create_project(teams=[self.team], organization=self.organization)
  636. self.login_as(user=self.user)
  637. def test_simple(self):
  638. release1 = Release.objects.create(
  639. organization_id=self.organization.id,
  640. version="1",
  641. date_added=datetime(2013, 8, 13, 3, 8, 24, 880386, tzinfo=pytz.UTC),
  642. )
  643. release1.add_project(self.project1)
  644. release2 = Release.objects.create(
  645. organization_id=self.organization.id,
  646. version="2",
  647. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386, tzinfo=pytz.UTC),
  648. date_released=datetime(2013, 8, 15, 3, 8, 24, 880386, tzinfo=pytz.UTC),
  649. )
  650. release2.add_project(self.project2)
  651. release3 = Release.objects.create(
  652. organization_id=self.organization.id,
  653. version="3",
  654. date_added=datetime(2013, 8, 14, 3, 8, 24, 880386, tzinfo=pytz.UTC),
  655. )
  656. release3.add_project(self.project3)
  657. url = reverse(
  658. "sentry-api-0-organization-releases-stats",
  659. kwargs={"organization_slug": self.organization.slug},
  660. )
  661. response = self.client.get(url, format="json")
  662. assert response.status_code == 200, response.content
  663. assert len(response.data) == 3
  664. assert response.data[0]["version"] == release3.version
  665. assert response.data[0]["date"] == release3.date_added
  666. assert response.data[1]["version"] == release1.version
  667. assert response.data[1]["date"] == release1.date_added
  668. assert response.data[2]["version"] == release2.version
  669. assert response.data[2]["date"] == release2.date_added
  670. def test_release_list_order_by_date_added(self):
  671. """
  672. Test that ensures that by relying on the default date sorting, releases
  673. will only be sorted according to `Release.date_added`, and
  674. `Release.date_released` should have no effect whatsoever on that order
  675. """
  676. user = self.create_user(is_staff=False, is_superuser=False)
  677. org = self.organization
  678. org.flags.allow_joinleave = False
  679. org.save()
  680. team = self.create_team(organization=org)
  681. project = self.create_project(teams=[team], organization=org)
  682. self.create_member(teams=[team], user=user, organization=org)
  683. self.login_as(user=user)
  684. release6 = Release.objects.create(
  685. organization_id=org.id,
  686. version="6",
  687. date_added=datetime(2013, 8, 10, 3, 8, 24, 880386),
  688. date_released=datetime(2013, 8, 20, 3, 8, 24, 880386),
  689. )
  690. release6.add_project(project)
  691. release7 = Release.objects.create(
  692. organization_id=org.id,
  693. version="7",
  694. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386),
  695. date_released=datetime(2013, 8, 18, 3, 8, 24, 880386),
  696. )
  697. release7.add_project(project)
  698. release8 = Release.objects.create(
  699. organization_id=org.id,
  700. version="8",
  701. date_added=datetime(2013, 8, 14, 3, 8, 24, 880386),
  702. date_released=datetime(2013, 8, 16, 3, 8, 24, 880386),
  703. )
  704. release8.add_project(project)
  705. url = reverse(
  706. "sentry-api-0-organization-releases-stats",
  707. kwargs={"organization_slug": self.organization.slug},
  708. )
  709. response = self.client.get(url, format="json")
  710. assert response.status_code == 200, response.content
  711. assert len(response.data) == 3
  712. assert response.data[0]["version"] == release8.version
  713. assert response.data[1]["version"] == release7.version
  714. assert response.data[2]["version"] == release6.version
  715. def test_with_adoption_stages(self):
  716. user = self.create_user(is_staff=False, is_superuser=False)
  717. org = self.organization
  718. org.save()
  719. team1 = self.create_team(organization=org)
  720. project1 = self.create_project(teams=[team1], organization=org)
  721. self.create_member(teams=[team1], user=user, organization=org)
  722. self.login_as(user=user)
  723. release1 = Release.objects.create(
  724. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  725. )
  726. release1.add_project(project1)
  727. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  728. response = self.client.get(url, format="json")
  729. assert response.status_code == 200, response.content
  730. assert len(response.data) == 1
  731. # Not returned because we don't have `adoptionStages=1`.
  732. assert "adoptionStages" not in response.data[0]
  733. response = self.client.get(f"{url}?adoptionStages=1", format="json")
  734. assert response.status_code == 200, response.content
  735. assert len(response.data) == 1
  736. assert "adoptionStages" in response.data[0]
  737. def test_semver_filter(self):
  738. self.login_as(user=self.user)
  739. release_1 = self.create_release(version="test@1.2.4")
  740. release_2 = self.create_release(version="test@1.2.3")
  741. release_3 = self.create_release(version="test2@1.2.5")
  742. self.create_release(version="some.release")
  743. response = self.get_success_response(self.organization.slug, query=f"{SEMVER_ALIAS}:>1.2.3")
  744. assert [r["version"] for r in response.data] == [release_3.version, release_1.version]
  745. response = self.get_success_response(
  746. self.organization.slug, query=f"{SEMVER_ALIAS}:>=1.2.3"
  747. )
  748. assert [r["version"] for r in response.data] == [
  749. release_3.version,
  750. release_2.version,
  751. release_1.version,
  752. ]
  753. response = self.get_success_response(self.organization.slug, query=f"{SEMVER_ALIAS}:1.2.*")
  754. assert [r["version"] for r in response.data] == [
  755. release_3.version,
  756. release_2.version,
  757. release_1.version,
  758. ]
  759. response = self.get_success_response(self.organization.slug, query=f"{SEMVER_ALIAS}:2.2.1")
  760. assert [r["version"] for r in response.data] == []
  761. response = self.get_success_response(
  762. self.organization.slug, query=f"{SEMVER_PACKAGE_ALIAS}:test2"
  763. )
  764. assert [r["version"] for r in response.data] == [release_3.version]
  765. response = self.get_success_response(
  766. self.organization.slug, query=f"{SEMVER_PACKAGE_ALIAS}:test"
  767. )
  768. assert [r["version"] for r in response.data] == [release_2.version, release_1.version]
  769. def test_release_stage_filter(self):
  770. self.login_as(user=self.user)
  771. response = self.get_success_response(
  772. self.organization.slug,
  773. query=f"{RELEASE_STAGE_ALIAS}:adopted",
  774. environment=self.environment.name,
  775. )
  776. assert [r["version"] for r in response.data] == []
  777. replaced_release = self.create_release(version="replaced_release")
  778. adopted_release = self.create_release(version="adopted_release")
  779. not_adopted_release = self.create_release(version="not_adopted_release")
  780. ReleaseProjectEnvironment.objects.create(
  781. project_id=self.project.id,
  782. release_id=adopted_release.id,
  783. environment_id=self.environment.id,
  784. adopted=timezone.now(),
  785. )
  786. ReleaseProjectEnvironment.objects.create(
  787. project_id=self.project.id,
  788. release_id=replaced_release.id,
  789. environment_id=self.environment.id,
  790. adopted=timezone.now(),
  791. unadopted=timezone.now(),
  792. )
  793. ReleaseProjectEnvironment.objects.create(
  794. project_id=self.project.id,
  795. release_id=not_adopted_release.id,
  796. environment_id=self.environment.id,
  797. )
  798. response = self.get_success_response(
  799. self.organization.slug,
  800. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.ADOPTED}",
  801. environment=self.environment.name,
  802. )
  803. assert [r["version"] for r in response.data] == [adopted_release.version]
  804. response = self.get_success_response(
  805. self.organization.slug,
  806. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.LOW_ADOPTION}",
  807. environment=self.environment.name,
  808. )
  809. assert [r["version"] for r in response.data] == [not_adopted_release.version]
  810. response = self.get_success_response(
  811. self.organization.slug,
  812. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.REPLACED}",
  813. environment=self.environment.name,
  814. )
  815. assert [r["version"] for r in response.data] == [replaced_release.version]
  816. response = self.get_success_response(
  817. self.organization.slug,
  818. query=f"{RELEASE_STAGE_ALIAS}:[{ReleaseStages.ADOPTED},{ReleaseStages.REPLACED}]",
  819. environment=self.environment.name,
  820. )
  821. assert [r["version"] for r in response.data] == [
  822. adopted_release.version,
  823. replaced_release.version,
  824. ]
  825. response = self.get_success_response(
  826. self.organization.slug,
  827. query=f"{RELEASE_STAGE_ALIAS}:[{ReleaseStages.LOW_ADOPTION}]",
  828. environment=self.environment.name,
  829. )
  830. assert [r["version"] for r in response.data] == [not_adopted_release.version]
  831. response = self.get_response(
  832. self.organization.slug,
  833. query=f"{RELEASE_STAGE_ALIAS}:invalid_stage",
  834. environment=self.environment.name,
  835. )
  836. assert response.status_code == 400
  837. response = self.get_response(
  838. self.organization.slug,
  839. query=f"{RELEASE_STAGE_ALIAS}:{ReleaseStages.ADOPTED}",
  840. # No environment
  841. )
  842. assert response.status_code == 400
  843. def test_multi_project_release_gets_filtered(self):
  844. multi_project_release = self.create_release(version="multi_project_release")
  845. single_project_release = self.create_release(version="single_project_release")
  846. project2 = self.create_project(teams=[self.team], organization=self.organization)
  847. # One project not adopted
  848. ReleaseProjectEnvironment.objects.create(
  849. project_id=self.project.id,
  850. release_id=multi_project_release.id,
  851. environment_id=self.environment.id,
  852. )
  853. # One project adopted
  854. ReleaseProjectEnvironment.objects.create(
  855. project_id=project2.id,
  856. release_id=multi_project_release.id,
  857. environment_id=self.environment.id,
  858. adopted=timezone.now(),
  859. )
  860. ReleaseProjectEnvironment.objects.create(
  861. project_id=self.project.id,
  862. release_id=single_project_release.id,
  863. environment_id=self.environment.id,
  864. adopted=timezone.now(),
  865. )
  866. # Filtering to self.environment.name and self.project with release.stage:adopted should NOT return multi_project_release.
  867. response = self.get_success_response(
  868. self.organization.slug,
  869. project=self.project.id,
  870. environment=self.environment.name,
  871. query=f"{RELEASE_STAGE_ALIAS}:adopted",
  872. )
  873. assert [r["version"] for r in response.data] == [single_project_release.version]
  874. response = self.get_success_response(
  875. self.organization.slug,
  876. environment=self.environment.name,
  877. query=f"{RELEASE_STAGE_ALIAS}:adopted",
  878. )
  879. assert [r["version"] for r in response.data] == [
  880. single_project_release.version,
  881. multi_project_release.version,
  882. ]
  883. def test_query_filter(self):
  884. self.login_as(user=self.user)
  885. release = self.create_release(
  886. self.project, version="foobar", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  887. )
  888. self.create_release(
  889. self.project, version="sdfsdfsdf", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  890. )
  891. response = self.get_success_response(self.organization.slug, query="oob")
  892. assert [r["version"] for r in response.data] == [release.version]
  893. response = self.get_success_response(self.organization.slug, query="baz")
  894. assert [r["version"] for r in response.data] == []
  895. response = self.get_success_response(self.organization.slug, query="release:*oob*")
  896. assert [r["version"] for r in response.data] == [release.version]
  897. response = self.get_success_response(self.organization.slug, query="release:foob*")
  898. assert [r["version"] for r in response.data] == [release.version]
  899. response = self.get_success_response(self.organization.slug, query="release:*bar")
  900. assert [r["version"] for r in response.data] == [release.version]
  901. response = self.get_success_response(self.organization.slug, query="release:foobar")
  902. assert [r["version"] for r in response.data] == [release.version]
  903. response = self.get_success_response(self.organization.slug, query="release:*baz*")
  904. assert [r["version"] for r in response.data] == []
  905. @region_silo_test(stable=True)
  906. class OrganizationReleaseCreateTest(APITestCase):
  907. def test_empty_release_version(self):
  908. user = self.create_user(is_staff=False, is_superuser=False)
  909. org = self.create_organization()
  910. org.flags.allow_joinleave = False
  911. org.save()
  912. team = self.create_team(organization=org)
  913. project = self.create_project(name="foo", organization=org, teams=[team])
  914. project2 = self.create_project(name="bar", organization=org, teams=[team])
  915. self.create_member(teams=[team], user=user, organization=org)
  916. self.login_as(user=user)
  917. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  918. response = self.client.post(
  919. url, data={"version": "", "projects": [project.slug, project2.slug]}
  920. )
  921. assert response.status_code == 400
  922. def test_minimal(self):
  923. user = self.create_user(is_staff=False, is_superuser=False)
  924. org = self.create_organization()
  925. org.flags.allow_joinleave = False
  926. org.save()
  927. team = self.create_team(organization=org)
  928. project = self.create_project(name="foo", organization=org, teams=[team])
  929. project2 = self.create_project(name="bar", organization=org, teams=[team])
  930. self.create_member(teams=[team], user=user, organization=org)
  931. self.login_as(user=user)
  932. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  933. response = self.client.post(
  934. url,
  935. data={"version": "1.2.1", "projects": [project.slug, project2.slug]},
  936. HTTP_USER_AGENT="sentry-cli/2.77.4",
  937. )
  938. assert response.status_code == 201, response.content
  939. assert response.data["version"]
  940. release = Release.objects.get(
  941. version=response.data["version"], user_agent="sentry-cli/2.77.4"
  942. )
  943. assert not release.owner_id
  944. assert release.organization == org
  945. assert ReleaseProject.objects.filter(release=release, project=project).exists()
  946. assert ReleaseProject.objects.filter(release=release, project=project2).exists()
  947. def test_duplicate(self):
  948. user = self.create_user(is_staff=False, is_superuser=False)
  949. org = self.create_organization()
  950. org.flags.allow_joinleave = False
  951. org.save()
  952. repo = Repository.objects.create(
  953. provider="dummy", name="my-org/my-repository", organization_id=org.id
  954. )
  955. team = self.create_team(organization=org)
  956. project = self.create_project(name="foo", organization=org, teams=[team])
  957. self.create_member(teams=[team], user=user, organization=org)
  958. self.login_as(user=user)
  959. release = Release.objects.create(version="1.2.1", organization=org)
  960. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  961. with self.tasks():
  962. response = self.client.post(
  963. url,
  964. data={
  965. "version": "1.2.1",
  966. "projects": [project.slug],
  967. "refs": [
  968. {
  969. "repository": "my-org/my-repository",
  970. "commit": "a" * 40,
  971. "previousCommit": "c" * 40,
  972. }
  973. ],
  974. },
  975. )
  976. release_commits1 = list(
  977. ReleaseCommit.objects.filter(release=release)
  978. .order_by("order")
  979. .values_list("commit__key", flat=True)
  980. )
  981. # check that commits are overwritten
  982. assert release_commits1 == [
  983. "62de626b7c7cfb8e77efb4273b1a3df4123e6216",
  984. "58de626b7c7cfb8e77efb4273b1a3df4123e6345",
  985. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  986. ]
  987. # should be 201 because project was added
  988. assert response.status_code == 201, response.content
  989. with self.tasks():
  990. with patch.object(DummyRepositoryProvider, "compare_commits") as mock_compare_commits:
  991. mock_compare_commits.return_value = [
  992. {"id": "c" * 40, "repository": repo.name},
  993. {"id": "d" * 40, "repository": repo.name},
  994. {"id": "a" * 40, "repository": repo.name},
  995. ]
  996. response2 = self.client.post(
  997. url,
  998. data={
  999. "version": "1.2.1",
  1000. "projects": [project.slug],
  1001. "refs": [
  1002. {
  1003. "repository": "my-org/my-repository",
  1004. "commit": "a" * 40,
  1005. "previousCommit": "b" * 40,
  1006. }
  1007. ],
  1008. },
  1009. )
  1010. release_commits2 = list(
  1011. ReleaseCommit.objects.filter(release=release)
  1012. .order_by("order")
  1013. .values_list("commit__key", flat=True)
  1014. )
  1015. # check that commits are overwritten
  1016. assert release_commits2 == [
  1017. "cccccccccccccccccccccccccccccccccccccccc",
  1018. "dddddddddddddddddddddddddddddddddddddddd",
  1019. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  1020. ]
  1021. assert response2.status_code == 208, response.content
  1022. assert Release.objects.filter(version="1.2.1", organization=org).count() == 1
  1023. # make sure project was added
  1024. assert ReleaseProject.objects.filter(release=release, project=project).exists()
  1025. def test_activity(self):
  1026. user = self.create_user(is_staff=False, is_superuser=False)
  1027. org = self.create_organization()
  1028. org.flags.allow_joinleave = False
  1029. org.save()
  1030. team = self.create_team(organization=org)
  1031. project = self.create_project(name="foo", organization=org, teams=[team])
  1032. project2 = self.create_project(name="bar", organization=org, teams=[team])
  1033. self.create_member(teams=[team], user=user, organization=org)
  1034. self.login_as(user=user)
  1035. release = Release.objects.create(
  1036. version="1.2.1", date_released=datetime.utcnow(), organization=org
  1037. )
  1038. release.add_project(project)
  1039. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1040. response = self.client.post(url, data={"version": "1.2.1", "projects": [project.slug]})
  1041. assert response.status_code == 208, response.content
  1042. response = self.client.post(
  1043. url, data={"version": "1.2.1", "projects": [project.slug, project2.slug]}
  1044. )
  1045. # should be 201 because 1 project was added
  1046. assert response.status_code == 201, response.content
  1047. assert not Activity.objects.filter(
  1048. type=ActivityType.RELEASE.value, project=project, ident=release.version
  1049. ).exists()
  1050. assert Activity.objects.filter(
  1051. type=ActivityType.RELEASE.value, project=project2, ident=release.version
  1052. ).exists()
  1053. def test_activity_with_long_release(self):
  1054. user = self.create_user(is_staff=False, is_superuser=False)
  1055. org = self.create_organization()
  1056. org.flags.allow_joinleave = False
  1057. org.save()
  1058. team = self.create_team(organization=org)
  1059. project = self.create_project(name="foo", organization=org, teams=[team])
  1060. project2 = self.create_project(name="bar", organization=org, teams=[team])
  1061. self.create_member(teams=[team], user=user, organization=org)
  1062. self.login_as(user=user)
  1063. release = Release.objects.create(
  1064. version="x" * 65, date_released=datetime.utcnow(), organization=org
  1065. )
  1066. release.add_project(project)
  1067. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1068. response = self.client.post(url, data={"version": "x" * 65, "projects": [project.slug]})
  1069. assert response.status_code == 208, response.content
  1070. response = self.client.post(
  1071. url, data={"version": "x" * 65, "projects": [project.slug, project2.slug]}
  1072. )
  1073. # should be 201 because 1 project was added
  1074. assert response.status_code == 201, response.content
  1075. assert not Activity.objects.filter(
  1076. type=ActivityType.RELEASE.value, project=project, ident=release.version[:64]
  1077. ).exists()
  1078. assert Activity.objects.filter(
  1079. type=ActivityType.RELEASE.value, project=project2, ident=release.version[:64]
  1080. ).exists()
  1081. def test_version_whitespace(self):
  1082. user = self.create_user(is_staff=False, is_superuser=False)
  1083. org = self.create_organization()
  1084. org.flags.allow_joinleave = False
  1085. org.save()
  1086. team = self.create_team(organization=org)
  1087. project = self.create_project(name="foo", organization=org, teams=[team])
  1088. self.create_member(teams=[team], user=user, organization=org)
  1089. self.login_as(user=user)
  1090. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1091. response = self.client.post(url, data={"version": "1.2.3\n", "projects": [project.slug]})
  1092. assert response.status_code == 400, response.content
  1093. response = self.client.post(url, data={"version": "\n1.2.3", "projects": [project.slug]})
  1094. assert response.status_code == 400, response.content
  1095. response = self.client.post(url, data={"version": "1.\n2.3", "projects": [project.slug]})
  1096. assert response.status_code == 400, response.content
  1097. response = self.client.post(url, data={"version": "1.2.3\f", "projects": [project.slug]})
  1098. assert response.status_code == 400, response.content
  1099. response = self.client.post(url, data={"version": "1.2.3\t", "projects": [project.slug]})
  1100. assert response.status_code == 400, response.content
  1101. response = self.client.post(url, data={"version": "1.2.3+dev", "projects": [project.slug]})
  1102. assert response.status_code == 201, response.content
  1103. assert response.data["version"] == "1.2.3+dev"
  1104. release = Release.objects.get(organization_id=org.id, version=response.data["version"])
  1105. assert not release.owner_id
  1106. def test_features(self):
  1107. user = self.create_user(is_staff=False, is_superuser=False)
  1108. org = self.create_organization()
  1109. org.flags.allow_joinleave = False
  1110. org.save()
  1111. team = self.create_team(organization=org)
  1112. project = self.create_project(name="foo", organization=org, teams=[team])
  1113. self.create_member(teams=[team], user=user, organization=org)
  1114. self.create_member(teams=[team], user=self.user, organization=org)
  1115. self.login_as(user=user)
  1116. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1117. response = self.client.post(
  1118. url, data={"version": "1.2.1", "owner": self.user.email, "projects": [project.slug]}
  1119. )
  1120. assert response.status_code == 201, response.content
  1121. assert response.data["version"]
  1122. release = Release.objects.get(organization_id=org.id, version=response.data["version"])
  1123. assert release.owner_id == self.user.id
  1124. def test_commits(self):
  1125. user = self.create_user(is_staff=False, is_superuser=False)
  1126. org = self.create_organization()
  1127. org.flags.allow_joinleave = False
  1128. org.save()
  1129. team = self.create_team(organization=org)
  1130. project = self.create_project(name="foo", organization=org, teams=[team])
  1131. self.create_member(teams=[team], user=user, organization=org)
  1132. self.login_as(user=user)
  1133. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1134. response = self.client.post(
  1135. url,
  1136. data={
  1137. "version": "1.2.1",
  1138. "commits": [{"id": "a" * 40}, {"id": "b" * 40}],
  1139. "projects": [project.slug],
  1140. },
  1141. )
  1142. assert response.status_code == 201, (response.status_code, response.content)
  1143. assert response.data["version"]
  1144. release = Release.objects.get(organization_id=org.id, version=response.data["version"])
  1145. rc_list = list(
  1146. ReleaseCommit.objects.filter(release=release)
  1147. .select_related("commit", "commit__author")
  1148. .order_by("order")
  1149. )
  1150. assert len(rc_list) == 2
  1151. for rc in rc_list:
  1152. assert rc.organization_id
  1153. @patch("sentry.tasks.commits.fetch_commits")
  1154. def test_commits_from_provider(self, mock_fetch_commits):
  1155. user = self.create_user(is_staff=False, is_superuser=False)
  1156. org = self.create_organization()
  1157. org.flags.allow_joinleave = False
  1158. org.save()
  1159. repo = Repository.objects.create(
  1160. organization_id=org.id, name="example/example", provider="dummy"
  1161. )
  1162. repo2 = Repository.objects.create(
  1163. organization_id=org.id, name="example/example2", provider="dummy"
  1164. )
  1165. team = self.create_team(organization=org)
  1166. project = self.create_project(name="foo", organization=org, teams=[team])
  1167. self.create_member(teams=[team], user=user, organization=org)
  1168. self.login_as(user=user)
  1169. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1170. self.client.post(
  1171. url,
  1172. data={
  1173. "version": "1",
  1174. "refs": [
  1175. {"commit": "0" * 40, "repository": repo.name},
  1176. {"commit": "0" * 40, "repository": repo2.name},
  1177. ],
  1178. "projects": [project.slug],
  1179. },
  1180. )
  1181. response = self.client.post(
  1182. url,
  1183. data={
  1184. "version": "1.2.1",
  1185. "refs": [
  1186. {"commit": "a" * 40, "repository": repo.name},
  1187. {"commit": "b" * 40, "repository": repo2.name},
  1188. ],
  1189. "projects": [project.slug],
  1190. },
  1191. )
  1192. assert response.status_code == 201
  1193. mock_fetch_commits.apply_async.assert_called_with(
  1194. kwargs={
  1195. "release_id": Release.objects.get(version="1.2.1", organization=org).id,
  1196. "user_id": user.id,
  1197. "refs": [
  1198. {"commit": "a" * 40, "repository": repo.name},
  1199. {"commit": "b" * 40, "repository": repo2.name},
  1200. ],
  1201. "prev_release_id": Release.objects.get(version="1", organization=org).id,
  1202. }
  1203. )
  1204. @patch("sentry.tasks.commits.fetch_commits")
  1205. def test_commits_from_provider_deprecated_head_commits(self, mock_fetch_commits):
  1206. user = self.create_user(is_staff=False, is_superuser=False)
  1207. org = self.create_organization()
  1208. org.flags.allow_joinleave = False
  1209. org.save()
  1210. repo = Repository.objects.create(
  1211. organization_id=org.id, name="example/example", provider="dummy"
  1212. )
  1213. repo2 = Repository.objects.create(
  1214. organization_id=org.id, name="example/example2", provider="dummy"
  1215. )
  1216. team = self.create_team(organization=org)
  1217. project = self.create_project(name="foo", organization=org, teams=[team])
  1218. self.create_member(teams=[team], user=user, organization=org)
  1219. self.login_as(user=user)
  1220. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1221. self.client.post(
  1222. url,
  1223. data={
  1224. "version": "1",
  1225. "headCommits": [
  1226. {"currentId": "0" * 40, "repository": repo.name},
  1227. {"currentId": "0" * 40, "repository": repo2.name},
  1228. ],
  1229. "projects": [project.slug],
  1230. },
  1231. )
  1232. response = self.client.post(
  1233. url,
  1234. data={
  1235. "version": "1.2.1",
  1236. "headCommits": [
  1237. {"currentId": "a" * 40, "repository": repo.name},
  1238. {"currentId": "b" * 40, "repository": repo2.name},
  1239. ],
  1240. "projects": [project.slug],
  1241. },
  1242. format="json",
  1243. )
  1244. mock_fetch_commits.apply_async.assert_called_with(
  1245. kwargs={
  1246. "release_id": Release.objects.get(version="1.2.1", organization=org).id,
  1247. "user_id": user.id,
  1248. "refs": [
  1249. {"commit": "a" * 40, "repository": repo.name, "previousCommit": None},
  1250. {"commit": "b" * 40, "repository": repo2.name, "previousCommit": None},
  1251. ],
  1252. "prev_release_id": Release.objects.get(version="1", organization=org).id,
  1253. }
  1254. )
  1255. assert response.status_code == 201
  1256. def test_commits_lock_conflict(self):
  1257. user = self.create_user(is_staff=False, is_superuser=False)
  1258. org = self.create_organization()
  1259. org.flags.allow_joinleave = False
  1260. org.save()
  1261. team = self.create_team(organization=org)
  1262. project = self.create_project(name="foo", organization=org, teams=[team])
  1263. self.create_member(teams=[team], user=user, organization=org)
  1264. self.login_as(user=user)
  1265. # Simulate a concurrent request by using an existing release
  1266. # that has its commit lock taken out.
  1267. release = self.create_release(project, self.user, version="1.2.1")
  1268. lock = locks.get(Release.get_lock_key(org.id, release.id), duration=10, name="release")
  1269. lock.acquire()
  1270. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1271. response = self.client.post(
  1272. url,
  1273. data={
  1274. "version": release.version,
  1275. "commits": [{"id": "a" * 40}, {"id": "b" * 40}],
  1276. "projects": [project.slug],
  1277. },
  1278. )
  1279. assert response.status_code == 409, (response.status_code, response.content)
  1280. assert "Release commits" in response.data["detail"]
  1281. def test_bad_project_slug(self):
  1282. user = self.create_user(is_staff=False, is_superuser=False)
  1283. org = self.create_organization()
  1284. org.flags.allow_joinleave = False
  1285. org.save()
  1286. team = self.create_team(organization=org)
  1287. project = self.create_project(name="foo", organization=org, teams=[team])
  1288. self.create_member(teams=[team], user=user, organization=org)
  1289. self.login_as(user=user)
  1290. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1291. response = self.client.post(
  1292. url, data={"version": "1.2.1", "projects": [project.slug, "banana"]}
  1293. )
  1294. assert response.status_code == 400
  1295. assert b"Invalid project slugs" in response.content
  1296. def test_project_permissions(self):
  1297. user = self.create_user(is_staff=False, is_superuser=False)
  1298. org = self.create_organization()
  1299. org.flags.allow_joinleave = False
  1300. org.save()
  1301. team1 = self.create_team(organization=org)
  1302. team2 = self.create_team(organization=org)
  1303. project1 = self.create_project(teams=[team1], organization=org)
  1304. project2 = self.create_project(teams=[team2], organization=org)
  1305. self.create_member(teams=[team1], user=user, organization=org)
  1306. self.login_as(user=user)
  1307. release1 = Release.objects.create(
  1308. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  1309. )
  1310. release1.add_project(project1)
  1311. release2 = Release.objects.create(
  1312. organization_id=org.id, version="2", date_added=datetime(2013, 8, 14, 3, 8, 24, 880386)
  1313. )
  1314. release2.add_project(project2)
  1315. release3 = Release.objects.create(
  1316. organization_id=org.id,
  1317. version="3",
  1318. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386),
  1319. date_released=datetime(2013, 8, 15, 3, 8, 24, 880386),
  1320. )
  1321. release3.add_project(project1)
  1322. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1323. response = self.client.post(
  1324. url, data={"version": "1.2.1", "projects": [project1.slug, project2.slug]}
  1325. )
  1326. assert response.status_code == 400
  1327. assert b"Invalid project slugs" in response.content
  1328. response = self.client.post(url, data={"version": "1.2.1", "projects": [project1.slug]})
  1329. assert response.status_code == 201, response.content
  1330. def test_api_key(self):
  1331. org = self.create_organization()
  1332. org.flags.allow_joinleave = False
  1333. org.save()
  1334. org2 = self.create_organization()
  1335. team1 = self.create_team(organization=org)
  1336. project1 = self.create_project(teams=[team1], organization=org)
  1337. release1 = Release.objects.create(
  1338. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  1339. )
  1340. release1.add_project(project1)
  1341. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1342. # test right org, wrong permissions level
  1343. with assume_test_silo_mode(SiloMode.CONTROL):
  1344. bad_api_key = ApiKey.objects.create(organization_id=org.id, scope_list=["project:read"])
  1345. response = self.client.post(
  1346. url,
  1347. data={"version": "1.2.1", "projects": [project1.slug]},
  1348. HTTP_AUTHORIZATION=self.create_basic_auth_header(bad_api_key.key),
  1349. )
  1350. assert response.status_code == 403
  1351. # test wrong org, right permissions level
  1352. with assume_test_silo_mode(SiloMode.CONTROL):
  1353. wrong_org_api_key = ApiKey.objects.create(
  1354. organization_id=org2.id, scope_list=["project:write"]
  1355. )
  1356. response = self.client.post(
  1357. url,
  1358. data={"version": "1.2.1", "projects": [project1.slug]},
  1359. HTTP_AUTHORIZATION=self.create_basic_auth_header(wrong_org_api_key.key),
  1360. )
  1361. assert response.status_code == 403
  1362. # test right org, right permissions level
  1363. with assume_test_silo_mode(SiloMode.CONTROL):
  1364. good_api_key = ApiKey.objects.create(
  1365. organization_id=org.id, scope_list=["project:write"]
  1366. )
  1367. response = self.client.post(
  1368. url,
  1369. data={"version": "1.2.1", "projects": [project1.slug]},
  1370. HTTP_AUTHORIZATION=self.create_basic_auth_header(good_api_key.key),
  1371. )
  1372. assert response.status_code == 201, response.content
  1373. def test_org_auth_token(self):
  1374. org = self.create_organization()
  1375. org.flags.allow_joinleave = False
  1376. org.save()
  1377. org2 = self.create_organization()
  1378. team1 = self.create_team(organization=org)
  1379. project1 = self.create_project(teams=[team1], organization=org)
  1380. release1 = Release.objects.create(
  1381. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  1382. )
  1383. release1.add_project(project1)
  1384. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1385. # test right org, wrong permissions level
  1386. with assume_test_silo_mode(SiloMode.CONTROL):
  1387. bad_token_str = generate_token(org.slug, "")
  1388. OrgAuthToken.objects.create(
  1389. organization_id=org.id,
  1390. name="token 1",
  1391. token_hashed=hash_token(bad_token_str),
  1392. token_last_characters="ABCD",
  1393. scope_list=[],
  1394. date_last_used=None,
  1395. )
  1396. response = self.client.post(
  1397. url,
  1398. data={"version": "1.2.1", "projects": [project1.slug]},
  1399. HTTP_AUTHORIZATION=f"Bearer {bad_token_str}",
  1400. )
  1401. assert response.status_code == 403
  1402. # test wrong org, right permissions level
  1403. with assume_test_silo_mode(SiloMode.CONTROL):
  1404. wrong_org_token_str = generate_token(org2.slug, "")
  1405. OrgAuthToken.objects.create(
  1406. organization_id=org2.id,
  1407. name="token 1",
  1408. token_hashed=hash_token(wrong_org_token_str),
  1409. token_last_characters="ABCD",
  1410. scope_list=["org:ci"],
  1411. date_last_used=None,
  1412. )
  1413. response = self.client.post(
  1414. url,
  1415. data={"version": "1.2.1", "projects": [project1.slug]},
  1416. HTTP_AUTHORIZATION=f"Bearer {wrong_org_token_str}",
  1417. )
  1418. assert response.status_code == 403
  1419. # test right org, right permissions level
  1420. with assume_test_silo_mode(SiloMode.CONTROL):
  1421. good_token_str = generate_token(org.slug, "")
  1422. OrgAuthToken.objects.create(
  1423. organization_id=org.id,
  1424. name="token 1",
  1425. token_hashed=hash_token(good_token_str),
  1426. token_last_characters="ABCD",
  1427. scope_list=["org:ci"],
  1428. date_last_used=None,
  1429. )
  1430. with outbox_runner():
  1431. response = self.client.post(
  1432. url,
  1433. data={"version": "1.2.1", "projects": [project1.slug]},
  1434. HTTP_AUTHORIZATION=f"Bearer {good_token_str}",
  1435. )
  1436. assert response.status_code == 201, response.content
  1437. # Make sure org token usage was updated
  1438. with assume_test_silo_mode(SiloMode.CONTROL):
  1439. org_token = OrgAuthToken.objects.get(token_hashed=hash_token(good_token_str))
  1440. assert org_token.date_last_used is not None
  1441. assert org_token.project_last_used_id == project1.id
  1442. @patch("sentry.tasks.commits.fetch_commits")
  1443. def test_api_token(self, mock_fetch_commits):
  1444. user = self.create_user(is_staff=False, is_superuser=False)
  1445. org = self.create_organization()
  1446. org.flags.allow_joinleave = False
  1447. org.save()
  1448. repo = Repository.objects.create(
  1449. organization_id=org.id, name="getsentry/sentry", provider="dummy"
  1450. )
  1451. repo2 = Repository.objects.create(
  1452. organization_id=org.id, name="getsentry/sentry-plugins", provider="dummy"
  1453. )
  1454. with assume_test_silo_mode(SiloMode.CONTROL):
  1455. api_token = ApiToken.objects.create(user=user, scope_list=["project:releases"])
  1456. team1 = self.create_team(organization=org)
  1457. self.create_member(teams=[team1], user=user, organization=org)
  1458. project1 = self.create_project(teams=[team1], organization=org)
  1459. release1 = Release.objects.create(
  1460. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  1461. )
  1462. release1.add_project(project1)
  1463. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1464. response = self.client.post(
  1465. url,
  1466. data={
  1467. "version": "1.2.1",
  1468. "refs": [
  1469. {"commit": "a" * 40, "repository": repo.name, "previousCommit": "c" * 40},
  1470. {"commit": "b" * 40, "repository": repo2.name},
  1471. ],
  1472. "projects": [project1.slug],
  1473. },
  1474. HTTP_AUTHORIZATION=f"Bearer {api_token.token}",
  1475. )
  1476. mock_fetch_commits.apply_async.assert_called_with(
  1477. kwargs={
  1478. "release_id": Release.objects.get(version="1.2.1", organization=org).id,
  1479. "user_id": user.id,
  1480. "refs": [
  1481. {"commit": "a" * 40, "repository": repo.name, "previousCommit": "c" * 40},
  1482. {"commit": "b" * 40, "repository": repo2.name},
  1483. ],
  1484. "prev_release_id": release1.id,
  1485. }
  1486. )
  1487. assert response.status_code == 201
  1488. def test_bad_repo_name(self):
  1489. user = self.create_user(is_staff=False, is_superuser=False)
  1490. org = self.create_organization()
  1491. org.flags.allow_joinleave = False
  1492. org.save()
  1493. team = self.create_team(organization=org)
  1494. project = self.create_project(name="foo", organization=org, teams=[team])
  1495. self.create_member(teams=[team], user=user, organization=org)
  1496. self.login_as(user=user)
  1497. url = reverse("sentry-api-0-organization-releases", kwargs={"organization_slug": org.slug})
  1498. response = self.client.post(
  1499. url,
  1500. data={
  1501. "version": "1.2.1",
  1502. "projects": [project.slug],
  1503. "refs": [{"repository": "not_a_repo", "commit": "a" * 40}],
  1504. },
  1505. )
  1506. assert response.status_code == 400
  1507. assert response.data == {"refs": ["Invalid repository names: not_a_repo"]}
  1508. @region_silo_test(stable=True)
  1509. class OrganizationReleaseCommitRangesTest(SetRefsTestCase):
  1510. def setUp(self):
  1511. super().setUp()
  1512. self.url = reverse(
  1513. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1514. )
  1515. @patch("sentry.tasks.commits.fetch_commits")
  1516. def test_simple(self, mock_fetch_commits):
  1517. refs = [
  1518. {
  1519. "repository": "test/repo",
  1520. "previousCommit": None,
  1521. "commit": "previous-commit-id..current-commit-id",
  1522. },
  1523. {
  1524. "repository": "test/repo",
  1525. "previousCommit": "previous-commit-will-be-ignored",
  1526. "commit": "previous-commit-id-2..current-commit-id-2",
  1527. },
  1528. {"repository": "test/repo", "commit": "previous-commit-id-3..current-commit-id-3"},
  1529. ]
  1530. response = self.client.post(
  1531. self.url, data={"version": "1", "refs": refs, "projects": [self.project.slug]}
  1532. )
  1533. assert response.status_code == 201
  1534. release = Release.objects.get(version="1", organization=self.org)
  1535. commits = Commit.objects.all().order_by("id")
  1536. self.assert_commit(commits[0], "current-commit-id")
  1537. self.assert_commit(commits[1], "current-commit-id-2")
  1538. self.assert_commit(commits[2], "current-commit-id-3")
  1539. head_commits = ReleaseHeadCommit.objects.all()
  1540. self.assert_head_commit(head_commits[0], "current-commit-id-3", release_id=release.id)
  1541. refs_expected = [
  1542. {
  1543. "repository": "test/repo",
  1544. "previousCommit": "previous-commit-id",
  1545. "commit": "current-commit-id",
  1546. },
  1547. {
  1548. "repository": "test/repo",
  1549. "previousCommit": "previous-commit-id-2",
  1550. "commit": "current-commit-id-2",
  1551. },
  1552. {
  1553. "repository": "test/repo",
  1554. "previousCommit": "previous-commit-id-3",
  1555. "commit": "current-commit-id-3",
  1556. },
  1557. ]
  1558. self.assert_fetch_commits(mock_fetch_commits, None, release.id, refs_expected)
  1559. @patch("sentry.tasks.commits.fetch_commits")
  1560. def test_head_commit(self, mock_fetch_commits):
  1561. headCommits = [
  1562. {
  1563. "currentId": "current-commit-id",
  1564. "previousId": "previous-commit-id",
  1565. "repository": self.repo.name,
  1566. },
  1567. {
  1568. "currentId": "current-commit-id-2",
  1569. "previousId": "previous-commit-id-2",
  1570. "repository": self.repo.name,
  1571. },
  1572. {
  1573. "currentId": "current-commit-id-3",
  1574. "previousId": "previous-commit-id-3",
  1575. "repository": self.repo.name,
  1576. },
  1577. ]
  1578. response = self.client.post(
  1579. self.url,
  1580. data={"version": "1", "headCommits": headCommits, "projects": [self.project.slug]},
  1581. )
  1582. assert response.status_code == 201
  1583. release = Release.objects.get(version="1", organization=self.org)
  1584. commits = Commit.objects.all().order_by("id")
  1585. self.assert_commit(commits[0], "current-commit-id")
  1586. self.assert_commit(commits[1], "current-commit-id-2")
  1587. self.assert_commit(commits[2], "current-commit-id-3")
  1588. head_commits = ReleaseHeadCommit.objects.all()
  1589. self.assert_head_commit(head_commits[0], "current-commit-id-3", release_id=release.id)
  1590. refs_expected = [
  1591. {
  1592. "repository": "test/repo",
  1593. "previousCommit": "previous-commit-id",
  1594. "commit": "current-commit-id",
  1595. },
  1596. {
  1597. "repository": "test/repo",
  1598. "previousCommit": "previous-commit-id-2",
  1599. "commit": "current-commit-id-2",
  1600. },
  1601. {
  1602. "repository": "test/repo",
  1603. "previousCommit": "previous-commit-id-3",
  1604. "commit": "current-commit-id-3",
  1605. },
  1606. ]
  1607. self.assert_fetch_commits(mock_fetch_commits, None, release.id, refs_expected)
  1608. @region_silo_test(stable=True)
  1609. class OrganizationReleaseListEnvironmentsTest(APITestCase):
  1610. def setUp(self):
  1611. self.login_as(user=self.user)
  1612. org = self.create_organization(owner=self.user)
  1613. team = self.create_team(organization=org, members=[self.user])
  1614. project1 = self.create_project(organization=org, teams=[team], name="foo")
  1615. project2 = self.create_project(organization=org, teams=[team], name="bar")
  1616. env1 = self.make_environment("prod", project1)
  1617. env2 = self.make_environment("staging", project2)
  1618. release1 = Release.objects.create(
  1619. organization_id=org.id, version="1", date_added=datetime(2013, 8, 13, 3, 8, 24, 880386)
  1620. )
  1621. release1.add_project(project1)
  1622. ReleaseProjectEnvironment.objects.create(
  1623. project_id=project1.id, release_id=release1.id, environment_id=env1.id
  1624. )
  1625. release2 = Release.objects.create(
  1626. organization_id=org.id, version="2", date_added=datetime(2013, 8, 14, 3, 8, 24, 880386)
  1627. )
  1628. release2.add_project(project2)
  1629. ReleaseProjectEnvironment.objects.create(
  1630. project_id=project2.id, release_id=release2.id, environment_id=env2.id
  1631. )
  1632. release3 = Release.objects.create(
  1633. organization_id=org.id,
  1634. version="3",
  1635. date_added=datetime(2013, 8, 12, 3, 8, 24, 880386),
  1636. date_released=datetime(2013, 8, 15, 3, 8, 24, 880386),
  1637. )
  1638. release3.add_project(project1)
  1639. ReleaseProjectEnvironment.objects.create(
  1640. project_id=project1.id, release_id=release3.id, environment_id=env2.id
  1641. )
  1642. release4 = Release.objects.create(organization_id=org.id, version="4")
  1643. release4.add_project(project2)
  1644. release5 = Release.objects.create(organization_id=org.id, version="5")
  1645. release5.add_project(project1)
  1646. release5.add_project(project2)
  1647. ReleaseProjectEnvironment.objects.create(
  1648. project_id=project1.id, release_id=release5.id, environment_id=env1.id
  1649. )
  1650. ReleaseProjectEnvironment.objects.create(
  1651. project_id=project2.id, release_id=release5.id, environment_id=env2.id
  1652. )
  1653. self.project1 = project1
  1654. self.project2 = project2
  1655. self.release1 = release1
  1656. self.release2 = release2
  1657. self.release3 = release3
  1658. self.release4 = release4
  1659. self.release5 = release5
  1660. self.env1 = env1
  1661. self.env2 = env2
  1662. self.org = org
  1663. def make_environment(self, name, project):
  1664. env = Environment.objects.create(organization_id=project.organization_id, name=name)
  1665. env.add_project(project)
  1666. return env
  1667. def assert_releases(self, response, releases):
  1668. assert response.status_code == 200, response.content
  1669. assert len(response.data) == len(releases)
  1670. response_versions = sorted(r["version"] for r in response.data)
  1671. releases_versions = sorted(r.version for r in releases)
  1672. assert response_versions == releases_versions
  1673. def test_environments_filter(self):
  1674. url = reverse(
  1675. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1676. )
  1677. response = self.client.get(url + "?environment=" + self.env1.name, format="json")
  1678. self.assert_releases(response, [self.release1, self.release5])
  1679. response = self.client.get(url + "?environment=" + self.env2.name, format="json")
  1680. self.assert_releases(response, [self.release2, self.release3, self.release5])
  1681. def test_empty_environment(self):
  1682. url = reverse(
  1683. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1684. )
  1685. env = self.make_environment("", self.project2)
  1686. ReleaseProjectEnvironment.objects.create(
  1687. project_id=self.project2.id, release_id=self.release4.id, environment_id=env.id
  1688. )
  1689. response = self.client.get(url + "?environment=", format="json")
  1690. self.assert_releases(response, [self.release4])
  1691. def test_all_environments(self):
  1692. url = reverse(
  1693. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1694. )
  1695. response = self.client.get(url, format="json")
  1696. self.assert_releases(
  1697. response, [self.release1, self.release2, self.release3, self.release4, self.release5]
  1698. )
  1699. def test_invalid_environment(self):
  1700. url = reverse(
  1701. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1702. )
  1703. response = self.client.get(url + "?environment=" + "invalid_environment", format="json")
  1704. assert response.status_code == 404
  1705. def test_specify_project_ids(self):
  1706. url = reverse(
  1707. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1708. )
  1709. response = self.client.get(url, format="json", data={"project": self.project1.id})
  1710. self.assert_releases(response, [self.release1, self.release3, self.release5])
  1711. response = self.client.get(url, format="json", data={"project": self.project2.id})
  1712. self.assert_releases(response, [self.release2, self.release4, self.release5])
  1713. response = self.client.get(
  1714. url, format="json", data={"project": [self.project1.id, self.project2.id]}
  1715. )
  1716. self.assert_releases(
  1717. response, [self.release1, self.release2, self.release3, self.release4, self.release5]
  1718. )
  1719. def test_date_range(self):
  1720. url = reverse(
  1721. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1722. )
  1723. response = self.client.get(
  1724. url,
  1725. format="json",
  1726. data={
  1727. "start": (datetime.now() - timedelta(days=1)).isoformat() + "Z",
  1728. "end": datetime.now().isoformat() + "Z",
  1729. },
  1730. )
  1731. self.assert_releases(response, [self.release4, self.release5])
  1732. def test_invalid_date_range(self):
  1733. url = reverse(
  1734. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1735. )
  1736. response = self.client.get(url, format="json", data={"start": "null", "end": "null"})
  1737. assert response.status_code == 400
  1738. @region_silo_test(stable=True)
  1739. class OrganizationReleaseCreateCommitPatch(ReleaseCommitPatchTest):
  1740. @cached_property
  1741. def url(self):
  1742. return reverse(
  1743. "sentry-api-0-organization-releases", kwargs={"organization_slug": self.org.slug}
  1744. )
  1745. def test_commits_with_patch_set(self):
  1746. response = self.client.post(
  1747. self.url,
  1748. data={
  1749. "version": "2d1ab93fe4bb42db80890f01f8358fc9f8fbff3b",
  1750. "projects": [self.project.slug],
  1751. "commits": [
  1752. {
  1753. "patch_set": [
  1754. {"path": "hello.py", "type": "M"},
  1755. {"path": "templates/hola.html", "type": "D"},
  1756. ],
  1757. "repository": "laurynsentry/helloworld",
  1758. "author_email": "lauryndbrown@gmail.com",
  1759. "timestamp": "2018-11-29T18:50:28+03:00",
  1760. "author_name": "Lauryn Brown",
  1761. "message": "made changes to hello.",
  1762. "id": "2d1ab93fe4bb42db80890f01f8358fc9f8fbff3b",
  1763. },
  1764. {
  1765. "patch_set": [
  1766. {"path": "templates/hello.html", "type": "M"},
  1767. {"path": "templates/goodbye.html", "type": "A"},
  1768. ],
  1769. "repository": "laurynsentry/helloworld",
  1770. "author_email": "lauryndbrown@gmail.com",
  1771. "timestamp": "2018-11-30T22:51:14+03:00",
  1772. "author_name": "Lauryn Brown",
  1773. "message": "Changed release",
  1774. "id": "be2fe070f6d1b8a572b67defc87af2582f9b0d78",
  1775. },
  1776. ],
  1777. },
  1778. )
  1779. assert response.status_code == 201, (response.status_code, response.content)
  1780. assert response.data["version"]
  1781. release = Release.objects.get(organization_id=self.org.id, version=response.data["version"])
  1782. repo = Repository.objects.get(organization_id=self.org.id, name="laurynsentry/helloworld")
  1783. assert repo.provider is None
  1784. rc_list = list(
  1785. ReleaseCommit.objects.filter(release=release)
  1786. .select_related("commit", "commit__author")
  1787. .order_by("order")
  1788. )
  1789. assert len(rc_list) == 2
  1790. for rc in rc_list:
  1791. assert rc.organization_id
  1792. author = CommitAuthor.objects.get(
  1793. organization_id=self.org.id, email="lauryndbrown@gmail.com"
  1794. )
  1795. assert author.name == "Lauryn Brown"
  1796. commits = [rc.commit for rc in rc_list]
  1797. commits.sort(key=lambda c: c.date_added)
  1798. self.assert_commit(
  1799. commit=commits[0],
  1800. repo_id=repo.id,
  1801. key="2d1ab93fe4bb42db80890f01f8358fc9f8fbff3b",
  1802. author_id=author.id,
  1803. message="made changes to hello.",
  1804. )
  1805. self.assert_commit(
  1806. commit=commits[1],
  1807. repo_id=repo.id,
  1808. key="be2fe070f6d1b8a572b67defc87af2582f9b0d78",
  1809. author_id=author.id,
  1810. message="Changed release",
  1811. )
  1812. file_changes = CommitFileChange.objects.filter(organization_id=self.org.id).order_by(
  1813. "filename"
  1814. )
  1815. self.assert_file_change(file_changes[0], "M", "hello.py", commits[0].id)
  1816. self.assert_file_change(file_changes[1], "A", "templates/goodbye.html", commits[1].id)
  1817. self.assert_file_change(file_changes[2], "M", "templates/hello.html", commits[1].id)
  1818. self.assert_file_change(file_changes[3], "D", "templates/hola.html", commits[0].id)
  1819. @region_silo_test(stable=True)
  1820. class ReleaseSerializerWithProjectsTest(TestCase):
  1821. def setUp(self):
  1822. super().setUp()
  1823. self.version = "1234567890"
  1824. self.repo_name = "repo/name"
  1825. self.repo2_name = "repo2/name"
  1826. self.commits = [{"id": "a" * 40}, {"id": "b" * 40}]
  1827. self.ref = "master"
  1828. self.url = "https://example.com"
  1829. self.dateReleased = "1000-10-10T06:06"
  1830. self.headCommits = [
  1831. {"currentId": "0" * 40, "repository": self.repo_name},
  1832. {"currentId": "0" * 40, "repository": self.repo2_name},
  1833. ]
  1834. self.refs = [
  1835. {"commit": "a" * 40, "previousCommit": "", "repository": self.repo_name},
  1836. {"commit": "b" * 40, "previousCommit": "", "repository": self.repo2_name},
  1837. ]
  1838. self.projects = ["project_slug", "project2_slug"]
  1839. def test_simple(self):
  1840. serializer = ReleaseSerializerWithProjects(
  1841. data={
  1842. "version": self.version,
  1843. "owner": self.user.username,
  1844. "ref": self.ref,
  1845. "url": self.url,
  1846. "dateReleased": self.dateReleased,
  1847. "commits": self.commits,
  1848. "headCommits": self.headCommits,
  1849. "refs": self.refs,
  1850. "projects": self.projects,
  1851. },
  1852. context={"organization": self.organization},
  1853. )
  1854. assert serializer.is_valid(), serializer.errors
  1855. assert sorted(serializer.fields.keys()) == sorted(
  1856. [
  1857. "version",
  1858. "owner",
  1859. "ref",
  1860. "url",
  1861. "dateReleased",
  1862. "commits",
  1863. "headCommits",
  1864. "refs",
  1865. "projects",
  1866. "status",
  1867. ]
  1868. )
  1869. result = serializer.validated_data
  1870. assert result["version"] == self.version
  1871. assert result["owner"]
  1872. assert result["owner"].id == self.user.id
  1873. assert result["owner"].username == self.user.username
  1874. assert result["ref"] == self.ref
  1875. assert result["url"] == self.url
  1876. assert result["dateReleased"] == datetime(1000, 10, 10, 6, 6, tzinfo=pytz.UTC)
  1877. assert result["commits"] == self.commits
  1878. assert result["headCommits"] == self.headCommits
  1879. assert result["refs"] == self.refs
  1880. assert result["projects"] == self.projects
  1881. def test_fields_not_required(self):
  1882. serializer = ReleaseSerializerWithProjects(
  1883. data={"version": self.version, "projects": self.projects},
  1884. context={"organization": self.organization},
  1885. )
  1886. assert serializer.is_valid()
  1887. result = serializer.validated_data
  1888. assert result["version"] == self.version
  1889. assert result["projects"] == self.projects
  1890. def test_do_not_allow_null_commits(self):
  1891. serializer = ReleaseSerializerWithProjects(
  1892. data={"version": self.version, "projects": self.projects, "commits": None},
  1893. context={"organization": self.organization},
  1894. )
  1895. assert not serializer.is_valid()
  1896. def test_do_not_allow_null_head_commits(self):
  1897. serializer = ReleaseSerializerWithProjects(
  1898. data={"version": self.version, "projects": self.projects, "headCommits": None},
  1899. context={"organization": self.organization},
  1900. )
  1901. assert not serializer.is_valid()
  1902. def test_do_not_allow_null_refs(self):
  1903. serializer = ReleaseSerializerWithProjects(
  1904. data={"version": self.version, "projects": self.projects, "refs": None},
  1905. context={"organization": self.organization},
  1906. )
  1907. assert not serializer.is_valid()
  1908. def test_ref_limited_by_max_version_length(self):
  1909. serializer = ReleaseSerializerWithProjects(
  1910. data={
  1911. "version": self.version,
  1912. "projects": self.projects,
  1913. "ref": "a" * MAX_VERSION_LENGTH,
  1914. },
  1915. context={"organization": self.organization},
  1916. )
  1917. assert serializer.is_valid()
  1918. serializer = ReleaseSerializerWithProjects(
  1919. data={
  1920. "version": self.version,
  1921. "projects": self.projects,
  1922. "ref": "a" * (MAX_VERSION_LENGTH + 1),
  1923. },
  1924. context={"organization": self.organization},
  1925. )
  1926. assert not serializer.is_valid()
  1927. def test_version_limited_by_max_version_length(self):
  1928. serializer = ReleaseSerializerWithProjects(
  1929. data={"version": "a" * MAX_VERSION_LENGTH, "projects": self.projects}
  1930. )
  1931. assert serializer.is_valid()
  1932. serializer = ReleaseSerializerWithProjects(
  1933. data={"version": "a" * (MAX_VERSION_LENGTH + 1), "projects": self.projects},
  1934. context={"organization": self.organization},
  1935. )
  1936. assert not serializer.is_valid()
  1937. def test_version_does_not_allow_whitespace(self):
  1938. for char in BAD_RELEASE_CHARS:
  1939. serializer = ReleaseSerializerWithProjects(
  1940. data={"version": char, "projects": self.projects},
  1941. context={"organization": self.organization},
  1942. )
  1943. assert not serializer.is_valid()
  1944. def test_version_does_not_allow_current_dir_path(self):
  1945. serializer = ReleaseSerializerWithProjects(data={"version": ".", "projects": self.projects})
  1946. assert not serializer.is_valid()
  1947. serializer = ReleaseSerializerWithProjects(
  1948. data={"version": "..", "projects": self.projects},
  1949. context={"organization": self.organization},
  1950. )
  1951. assert not serializer.is_valid()
  1952. def test_version_does_not_allow_null_or_empty_value(self):
  1953. serializer = ReleaseSerializerWithProjects(
  1954. data={"version": None, "projects": self.projects},
  1955. context={"organization": self.organization},
  1956. )
  1957. assert not serializer.is_valid()
  1958. serializer = ReleaseSerializerWithProjects(
  1959. data={"version": "", "projects": self.projects},
  1960. context={"organization": self.organization},
  1961. )
  1962. assert not serializer.is_valid()
  1963. def test_version_cannot_be_latest(self):
  1964. serializer = ReleaseSerializerWithProjects(
  1965. data={"version": "Latest", "projects": self.projects},
  1966. context={"organization": self.organization},
  1967. )
  1968. assert not serializer.is_valid()
  1969. @region_silo_test(stable=True)
  1970. class ReleaseHeadCommitSerializerTest(unittest.TestCase):
  1971. def setUp(self):
  1972. super().setUp()
  1973. self.repo_name = "repo/name"
  1974. self.commit = "b" * 40
  1975. self.commit_range = "{}..{}".format("a" * 40, "b" * 40)
  1976. self.prev_commit = "a" * 40
  1977. def test_simple(self):
  1978. serializer = ReleaseHeadCommitSerializer(
  1979. data={
  1980. "commit": self.commit,
  1981. "previousCommit": self.prev_commit,
  1982. "repository": self.repo_name,
  1983. }
  1984. )
  1985. assert serializer.is_valid()
  1986. assert sorted(serializer.fields.keys()) == sorted(
  1987. ["commit", "previousCommit", "repository"]
  1988. )
  1989. result = serializer.validated_data
  1990. assert result["commit"] == self.commit
  1991. assert result["previousCommit"] == self.prev_commit
  1992. assert result["repository"] == self.repo_name
  1993. def test_prev_commit_not_required(self):
  1994. serializer = ReleaseHeadCommitSerializer(
  1995. data={"commit": self.commit, "previousCommit": None, "repository": self.repo_name}
  1996. )
  1997. assert serializer.is_valid()
  1998. def test_do_not_allow_null_or_empty_commit_or_repo(self):
  1999. serializer = ReleaseHeadCommitSerializer(
  2000. data={"commit": None, "previousCommit": self.prev_commit, "repository": self.repo_name}
  2001. )
  2002. assert not serializer.is_valid()
  2003. serializer = ReleaseHeadCommitSerializer(
  2004. data={"commit": "", "previousCommit": self.prev_commit, "repository": self.repo_name}
  2005. )
  2006. assert not serializer.is_valid()
  2007. serializer = ReleaseHeadCommitSerializer(
  2008. data={"commit": self.commit, "previousCommit": self.prev_commit, "repository": None}
  2009. )
  2010. assert not serializer.is_valid()
  2011. serializer = ReleaseHeadCommitSerializer(
  2012. data={"commit": self.commit, "previousCommit": self.prev_commit, "repository": ""}
  2013. )
  2014. assert not serializer.is_valid()
  2015. def test_single_commit_limited_by_max_commit_length(self):
  2016. serializer = ReleaseHeadCommitSerializer(
  2017. data={"commit": "b" * MAX_COMMIT_LENGTH, "repository": self.repo_name}
  2018. )
  2019. assert serializer.is_valid()
  2020. serializer = ReleaseHeadCommitSerializer(
  2021. data={
  2022. "commit": self.commit,
  2023. "previousCommit": "a" * MAX_COMMIT_LENGTH,
  2024. "repository": self.repo_name,
  2025. }
  2026. )
  2027. assert serializer.is_valid()
  2028. serializer = ReleaseHeadCommitSerializer(
  2029. data={"commit": "b" * (MAX_COMMIT_LENGTH + 1), "repository": self.repo_name}
  2030. )
  2031. assert not serializer.is_valid()
  2032. serializer = ReleaseHeadCommitSerializer(
  2033. data={
  2034. "commit": self.commit,
  2035. "previousCommit": "a" * (MAX_COMMIT_LENGTH + 1),
  2036. "repository": self.repo_name,
  2037. }
  2038. )
  2039. assert not serializer.is_valid()
  2040. def test_commit_range_does_not_allow_empty_commits(self):
  2041. serializer = ReleaseHeadCommitSerializer(
  2042. data={
  2043. "commit": "{}..{}".format("", "b" * MAX_COMMIT_LENGTH),
  2044. "repository": self.repo_name,
  2045. }
  2046. )
  2047. assert not serializer.is_valid()
  2048. serializer = ReleaseHeadCommitSerializer(
  2049. data={
  2050. "commit": "{}..{}".format("a" * MAX_COMMIT_LENGTH, ""),
  2051. "repository": self.repo_name,
  2052. }
  2053. )
  2054. assert not serializer.is_valid()
  2055. def test_commit_range_limited_by_max_commit_length(self):
  2056. serializer = ReleaseHeadCommitSerializer(
  2057. data={
  2058. "commit": "{}..{}".format("a" * MAX_COMMIT_LENGTH, "b" * MAX_COMMIT_LENGTH),
  2059. "repository": self.repo_name,
  2060. }
  2061. )
  2062. assert serializer.is_valid()
  2063. serializer = ReleaseHeadCommitSerializer(
  2064. data={
  2065. "commit": "{}..{}".format("a" * (MAX_COMMIT_LENGTH + 1), "b" * MAX_COMMIT_LENGTH),
  2066. "repository": self.repo_name,
  2067. }
  2068. )
  2069. assert not serializer.is_valid()
  2070. serializer = ReleaseHeadCommitSerializer(
  2071. data={
  2072. "commit": "{}..{}".format("a" * MAX_COMMIT_LENGTH, "b" * (MAX_COMMIT_LENGTH + 1)),
  2073. "repository": self.repo_name,
  2074. }
  2075. )
  2076. assert not serializer.is_valid()