test_organization_actions.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import re
  2. import pytest
  3. from sentry.models import (
  4. Organization,
  5. OrganizationStatus,
  6. OutboxCategory,
  7. OutboxScope,
  8. RegionOutbox,
  9. outbox_context,
  10. )
  11. from sentry.services.hybrid_cloud.organization_actions.impl import (
  12. create_organization_with_outbox_message,
  13. generate_deterministic_organization_slug,
  14. mark_organization_as_pending_deletion_with_outbox_message,
  15. unmark_organization_as_pending_deletion_with_outbox_message,
  16. update_organization_with_outbox_message,
  17. upsert_organization_by_org_id_with_outbox_message,
  18. )
  19. from sentry.testutils.cases import TestCase
  20. from sentry.testutils.silo import region_silo_test
  21. def assert_outbox_update_message_exists(org: Organization, expected_count: int):
  22. outbox_messages = RegionOutbox.objects.filter()
  23. # TODO(HC): Remove this once we can ensure an expected count of 1 for every message
  24. # It's not essential since these messages will coallesce, but there's no reason we
  25. # should be queueing 2 outbox messages per create/update
  26. assert outbox_messages.count() == expected_count
  27. for org_update_outbox in outbox_messages:
  28. assert org_update_outbox.shard_identifier == org.id
  29. assert org_update_outbox.shard_scope == OutboxScope.ORGANIZATION_SCOPE
  30. assert org_update_outbox.category == OutboxCategory.ORGANIZATION_UPDATE
  31. @region_silo_test(stable=True)
  32. class OrganizationUpdateTest(TestCase):
  33. def setUp(self):
  34. self.org: Organization = self.create_organization(slug="sluggy", name="barfoo")
  35. def test_create_organization_with_outbox_message(self):
  36. with outbox_context(flush=False):
  37. org: Organization = create_organization_with_outbox_message(
  38. create_options={
  39. "slug": "santry",
  40. "name": "santry",
  41. "status": OrganizationStatus.ACTIVE,
  42. }
  43. )
  44. assert org.id
  45. assert org.slug == "santry"
  46. assert org.name == "santry"
  47. assert_outbox_update_message_exists(org=org, expected_count=1)
  48. @region_silo_test(stable=True)
  49. class OrganizationUpdateWithOutboxTest(TestCase):
  50. def setUp(self):
  51. self.org: Organization = self.create_organization(slug="sluggy", name="barfoo")
  52. def test_update_organization_with_outbox_message(self):
  53. with outbox_context(flush=False):
  54. update_organization_with_outbox_message(
  55. org_id=self.org.id, update_data={"name": "foobar"}
  56. )
  57. self.org.refresh_from_db()
  58. assert self.org.name == "foobar"
  59. assert self.org.slug == "sluggy"
  60. assert_outbox_update_message_exists(org=self.org, expected_count=1)
  61. def test_update_with_missing_org_id(self):
  62. with pytest.raises(Organization.DoesNotExist):
  63. update_organization_with_outbox_message(org_id=1234, update_data={"name": "foobar"})
  64. @region_silo_test(stable=True)
  65. class OrganizationUpsertWithOutboxTest(TestCase):
  66. def setUp(self):
  67. self.org: Organization = self.create_organization(slug="sluggy", name="barfoo")
  68. def test_upsert_queues_outbox_message_and_updates_org(self):
  69. # The test fixture creates at least 1 org so comparing count before
  70. # and after the upsert is the safest way to assert we haven't created
  71. # a new entry.
  72. previous_org_count = Organization.objects.count()
  73. org_before_modification = Organization.objects.get(id=self.org.id)
  74. with outbox_context(flush=False):
  75. updated_org: Organization = upsert_organization_by_org_id_with_outbox_message(
  76. org_id=self.org.id,
  77. upsert_data={
  78. "slug": "foobar",
  79. "status": OrganizationStatus.DELETION_IN_PROGRESS,
  80. },
  81. )
  82. assert Organization.objects.count() == previous_org_count
  83. self.org.refresh_from_db()
  84. assert updated_org.slug == self.org.slug == "foobar"
  85. assert updated_org.name == self.org.name == "barfoo"
  86. assert updated_org.status == self.org.status == OrganizationStatus.DELETION_IN_PROGRESS
  87. assert (
  88. updated_org.default_role
  89. == self.org.default_role
  90. == org_before_modification.default_role
  91. )
  92. assert_outbox_update_message_exists(org=self.org, expected_count=1)
  93. def test_upsert_creates_organization_with_desired_id(self):
  94. previous_org_count = Organization.objects.count()
  95. org_before_modification = Organization.objects.get(id=self.org.id)
  96. desired_org_id = 1234
  97. with outbox_context(flush=False):
  98. created_org: Organization = upsert_organization_by_org_id_with_outbox_message(
  99. org_id=desired_org_id,
  100. upsert_data={
  101. "slug": "random",
  102. "name": "rando",
  103. "status": OrganizationStatus.ACTIVE,
  104. },
  105. )
  106. assert Organization.objects.count() == previous_org_count + 1
  107. db_created_org = Organization.objects.get(id=desired_org_id)
  108. assert db_created_org.slug == created_org.slug == "random"
  109. assert db_created_org.status == created_org.status == OrganizationStatus.ACTIVE
  110. assert db_created_org.name == created_org.name == "rando"
  111. # Probably overly cautious, but assert that previous org has not been modified
  112. self.org.refresh_from_db()
  113. assert org_before_modification.slug == self.org.slug
  114. assert org_before_modification.name == self.org.name
  115. assert org_before_modification.status == self.org.status
  116. assert_outbox_update_message_exists(org=db_created_org, expected_count=1)
  117. @region_silo_test(stable=True)
  118. class OrganizationMarkOrganizationAsPendingDeletionWithOutboxMessageTest(TestCase):
  119. def setUp(self):
  120. self.org: Organization = self.create_organization(
  121. slug="sluggy", name="barfoo", status=OrganizationStatus.ACTIVE
  122. )
  123. def test_mark_for_deletion_and_outbox_generation(self):
  124. org_before_update = Organization.objects.get(id=self.org.id)
  125. with outbox_context(flush=False):
  126. updated_org = mark_organization_as_pending_deletion_with_outbox_message(
  127. org_id=self.org.id
  128. )
  129. assert updated_org
  130. self.org.refresh_from_db()
  131. assert updated_org.status == self.org.status == OrganizationStatus.PENDING_DELETION
  132. assert updated_org.name == self.org.name == org_before_update.name
  133. assert updated_org.slug == self.org.slug == org_before_update.slug
  134. assert_outbox_update_message_exists(self.org, 1)
  135. def test_mark_for_deletion_on_already_deleted_org(self):
  136. self.org.status = OrganizationStatus.PENDING_DELETION
  137. self.org.save()
  138. org_before_update = Organization.objects.get(id=self.org.id)
  139. with outbox_context(flush=False):
  140. updated_org = mark_organization_as_pending_deletion_with_outbox_message(
  141. org_id=self.org.id
  142. )
  143. assert updated_org is None
  144. self.org.refresh_from_db()
  145. assert self.org.status == org_before_update.status
  146. assert self.org.name == org_before_update.name
  147. assert self.org.slug == org_before_update.slug
  148. assert_outbox_update_message_exists(self.org, 0)
  149. @region_silo_test(stable=True)
  150. class UnmarkOrganizationForDeletionWithOutboxMessageTest(TestCase):
  151. def setUp(self):
  152. self.org: Organization = self.create_organization(
  153. slug="sluggy", name="barfoo", status=OrganizationStatus.PENDING_DELETION
  154. )
  155. def test_unmark_for_pending_deletion_and_outbox_generation(self):
  156. with outbox_context(flush=False):
  157. updated_org = unmark_organization_as_pending_deletion_with_outbox_message(
  158. org_id=self.org.id
  159. )
  160. assert updated_org
  161. self.org.refresh_from_db()
  162. assert updated_org.status == self.org.status == OrganizationStatus.ACTIVE
  163. assert updated_org.name == self.org.name
  164. assert updated_org.slug == self.org.slug
  165. assert_outbox_update_message_exists(self.org, 1)
  166. def test_unmark_for_deletion_in_progress_and_outbox_generation(self):
  167. update_organization_with_outbox_message(
  168. org_id=self.org.id, update_data={"status": OrganizationStatus.DELETION_IN_PROGRESS}
  169. )
  170. with outbox_context(flush=False):
  171. updated_org = unmark_organization_as_pending_deletion_with_outbox_message(
  172. org_id=self.org.id
  173. )
  174. assert updated_org
  175. self.org.refresh_from_db()
  176. assert updated_org.status == self.org.status == OrganizationStatus.ACTIVE
  177. assert updated_org.name == self.org.name
  178. assert updated_org.slug == self.org.slug
  179. assert_outbox_update_message_exists(self.org, 1)
  180. def test_unmark_org_when_already_active(self):
  181. update_organization_with_outbox_message(
  182. org_id=self.org.id, update_data={"status": OrganizationStatus.ACTIVE}
  183. )
  184. org_before_update = Organization.objects.get(id=self.org.id)
  185. with outbox_context(flush=False):
  186. updated_org = unmark_organization_as_pending_deletion_with_outbox_message(
  187. org_id=self.org.id
  188. )
  189. assert not updated_org
  190. self.org.refresh_from_db()
  191. assert self.org.status == org_before_update.status
  192. assert self.org.name == org_before_update.name
  193. assert self.org.slug == org_before_update.slug
  194. assert_outbox_update_message_exists(self.org, 0)
  195. class TestGenerateDeterministicOrganizationSlug(TestCase):
  196. def test_slug_under_size_limit(self):
  197. slug = generate_deterministic_organization_slug(
  198. desired_slug_base="santry", desired_org_name="santry", owning_user_id=42
  199. )
  200. assert slug == "santry-095a9012d"
  201. def test_slug_above_size_limit(self):
  202. slug = generate_deterministic_organization_slug(
  203. desired_slug_base="areallylongsentryorgnamethatiswaytoolong",
  204. desired_org_name="santry",
  205. owning_user_id=42,
  206. )
  207. assert len(slug) == 30
  208. assert slug == "areallylongsentryorg-945bda148"
  209. def test_slug_with_mixed_casing(self):
  210. slug = generate_deterministic_organization_slug(
  211. desired_slug_base="A mixed CASING str",
  212. desired_org_name="santry",
  213. owning_user_id=42,
  214. )
  215. assert slug == "a-mixed-casing-str-9e9173167"
  216. def test_slug_with_unicode_chars(self):
  217. unicoded_str = "Sí Señtry 😅"
  218. slug = generate_deterministic_organization_slug(
  219. desired_slug_base=unicoded_str, desired_org_name=unicoded_str, owning_user_id=42
  220. )
  221. assert slug == "si-sentry-3471b1b85"
  222. def test_slug_with_0_length(self):
  223. unicoded_str = "😅"
  224. slug = generate_deterministic_organization_slug(
  225. desired_slug_base=unicoded_str, desired_org_name=unicoded_str, owning_user_id=42
  226. )
  227. random_slug_regex = re.compile(r"^[a-f0-9]{10}-[a-f0-9]{9}")
  228. assert random_slug_regex.match(slug)
  229. slug = generate_deterministic_organization_slug(
  230. desired_slug_base="", desired_org_name=unicoded_str, owning_user_id=42
  231. )
  232. assert random_slug_regex.match(slug)