test_api.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. from functools import cached_property
  2. from unittest import mock
  3. from django.conf import settings
  4. from django.test import override_settings
  5. from django.urls import reverse
  6. from sentry import options
  7. from sentry.api.utils import generate_region_url
  8. from sentry.auth import superuser
  9. from sentry.models.apitoken import ApiToken
  10. from sentry.models.organization import Organization, OrganizationStatus
  11. from sentry.models.organizationmember import OrganizationMember
  12. from sentry.models.scheduledeletion import RegionScheduledDeletion
  13. from sentry.silo.base import SiloMode
  14. from sentry.tasks.deletion.scheduled import run_deletion
  15. from sentry.testutils.cases import TestCase
  16. from sentry.testutils.helpers.features import with_feature
  17. from sentry.testutils.silo import assume_test_silo_mode, create_test_regions, region_silo_test
  18. from sentry.utils import json
  19. @region_silo_test
  20. class CrossDomainXmlTest(TestCase):
  21. @cached_property
  22. def path(self):
  23. return reverse("sentry-api-crossdomain-xml", kwargs={"project_id": self.project.id})
  24. def test_inaccessible_in_control_silo(self):
  25. with override_settings(SILO_MODE=SiloMode.CONTROL):
  26. resp = self.client.get(self.path)
  27. assert resp.status_code == 404
  28. @mock.patch("sentry.web.api.get_origins")
  29. def test_output_with_global(self, get_origins):
  30. get_origins.return_value = "*"
  31. resp = self.client.get(self.path)
  32. get_origins.assert_called_once_with(self.project)
  33. assert resp.status_code == 200, resp.content
  34. self.assertEqual(resp["Content-Type"], "application/xml")
  35. self.assertTemplateUsed(resp, "sentry/crossdomain.xml")
  36. assert b'<allow-access-from domain="*" secure="false" />' in resp.content
  37. @mock.patch("sentry.web.api.get_origins")
  38. def test_output_with_allowed_origins(self, get_origins):
  39. get_origins.return_value = ["disqus.com", "www.disqus.com"]
  40. resp = self.client.get(self.path)
  41. get_origins.assert_called_once_with(self.project)
  42. self.assertEqual(resp.status_code, 200)
  43. self.assertEqual(resp["Content-Type"], "application/xml")
  44. self.assertTemplateUsed(resp, "sentry/crossdomain.xml")
  45. assert b'<allow-access-from domain="disqus.com" secure="false" />' in resp.content
  46. assert b'<allow-access-from domain="www.disqus.com" secure="false" />' in resp.content
  47. @mock.patch("sentry.web.api.get_origins")
  48. def test_output_with_no_origins(self, get_origins):
  49. get_origins.return_value = []
  50. resp = self.client.get(self.path)
  51. get_origins.assert_called_once_with(self.project)
  52. self.assertEqual(resp.status_code, 200)
  53. self.assertEqual(resp["Content-Type"], "application/xml")
  54. self.assertTemplateUsed(resp, "sentry/crossdomain.xml")
  55. assert b"<allow-access-from" not in resp.content
  56. def test_output_allows_x_sentry_auth(self):
  57. resp = self.client.get(self.path)
  58. self.assertEqual(resp.status_code, 200)
  59. self.assertEqual(resp["Content-Type"], "application/xml")
  60. self.assertTemplateUsed(resp, "sentry/crossdomain.xml")
  61. assert (
  62. b'<allow-http-request-headers-from domain="*" headers="*" secure="false" />'
  63. in resp.content
  64. )
  65. class RobotsTxtTest(TestCase):
  66. @cached_property
  67. def path(self):
  68. return reverse("sentry-robots-txt")
  69. def test_robots(self):
  70. resp = self.client.get(self.path)
  71. assert resp.status_code == 200
  72. assert resp["Content-Type"] == "text/plain"
  73. @region_silo_test(regions=create_test_regions("us", "eu"), include_monolith_run=True)
  74. class ClientConfigViewTest(TestCase):
  75. @cached_property
  76. def path(self):
  77. return reverse("sentry-api-client-config")
  78. def test_cookie_names(self):
  79. resp = self.client.get(self.path)
  80. assert resp.status_code == 200
  81. assert resp["Content-Type"] == "application/json"
  82. data = json.loads(resp.content)
  83. assert data["csrfCookieName"] == "sc"
  84. assert data["csrfCookieName"] == settings.CSRF_COOKIE_NAME
  85. assert data["superUserCookieName"] == "su"
  86. assert data["superUserCookieName"] == superuser.COOKIE_NAME
  87. def test_has_user_registration(self):
  88. with self.options({"auth.allow-registration": True}):
  89. resp = self.client.get(self.path)
  90. assert resp.status_code == 200
  91. assert resp["Content-Type"] == "application/json"
  92. data = json.loads(resp.content)
  93. assert data["features"] == ["organizations:create", "auth:register"]
  94. with self.options({"auth.allow-registration": False}):
  95. resp = self.client.get(self.path)
  96. assert resp.status_code == 200
  97. assert resp["Content-Type"] == "application/json"
  98. data = json.loads(resp.content)
  99. assert data["features"] == ["organizations:create"]
  100. def test_org_create_feature(self):
  101. with self.feature({"organizations:create": True}):
  102. resp = self.client.get(self.path)
  103. assert resp.status_code == 200
  104. assert resp["Content-Type"] == "application/json"
  105. data = json.loads(resp.content)
  106. assert data["features"] == ["organizations:create"]
  107. with self.feature({"organizations:create": False}):
  108. resp = self.client.get(self.path)
  109. assert resp.status_code == 200
  110. assert resp["Content-Type"] == "application/json"
  111. data = json.loads(resp.content)
  112. assert data["features"] == []
  113. def test_customer_domain_feature(self):
  114. self.login_as(self.user)
  115. # Induce last active organization
  116. resp = self.client.get(
  117. reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
  118. )
  119. assert resp.status_code == 200
  120. assert resp["Content-Type"] == "application/json"
  121. with self.feature({"organizations:customer-domains": True}):
  122. resp = self.client.get(self.path)
  123. assert resp.status_code == 200
  124. assert resp["Content-Type"] == "application/json"
  125. data = json.loads(resp.content)
  126. assert data["lastOrganization"] == self.organization.slug
  127. assert data["features"] == ["organizations:create", "organizations:customer-domains"]
  128. with self.feature({"organizations:customer-domains": False}):
  129. resp = self.client.get(self.path)
  130. assert resp.status_code == 200
  131. assert resp["Content-Type"] == "application/json"
  132. data = json.loads(resp.content)
  133. assert data["features"] == ["organizations:create"]
  134. # Customer domain feature is injected if a customer domain is used.
  135. resp = self.client.get(self.path, HTTP_HOST="albertos-apples.testserver")
  136. assert resp.status_code == 200
  137. assert resp["Content-Type"] == "application/json"
  138. data = json.loads(resp.content)
  139. assert data["features"] == ["organizations:create", "organizations:customer-domains"]
  140. def test_unauthenticated(self):
  141. resp = self.client.get(self.path)
  142. assert resp.status_code == 200
  143. assert resp["Content-Type"] == "application/json"
  144. data = json.loads(resp.content)
  145. assert not data["isAuthenticated"]
  146. assert data["user"] is None
  147. assert data["features"] == ["organizations:create"]
  148. assert data["customerDomain"] is None
  149. def test_authenticated(self):
  150. user = self.create_user("foo@example.com")
  151. self.login_as(user)
  152. resp = self.client.get(self.path)
  153. assert resp.status_code == 200
  154. assert resp["Content-Type"] == "application/json"
  155. data = json.loads(resp.content)
  156. assert data["isAuthenticated"]
  157. assert data["user"]
  158. assert data["user"]["email"] == user.email
  159. assert data["features"] == ["organizations:create"]
  160. assert data["customerDomain"] is None
  161. def _run_test_with_privileges(self, is_superuser: bool, is_staff: bool):
  162. user = self.create_user("foo@example.com", is_superuser=is_superuser, is_staff=is_staff)
  163. self.create_organization(owner=user)
  164. self.login_as(user, superuser=is_superuser, staff=is_staff)
  165. other_org = self.create_organization()
  166. with mock.patch("sentry.auth.superuser.SUPERUSER_ORG_ID", self.organization.id):
  167. resp = self.client.get(self.path)
  168. assert resp.status_code == 200
  169. assert resp["Content-Type"] == "application/json"
  170. data = json.loads(resp.content)
  171. assert data["isAuthenticated"]
  172. assert data["user"]
  173. assert data["user"]["email"] == user.email
  174. assert data["user"]["isSuperuser"] is is_superuser
  175. assert data["lastOrganization"] is None
  176. if is_superuser:
  177. assert data["links"] == {
  178. "organizationUrl": None,
  179. "regionUrl": None,
  180. "sentryUrl": "http://testserver",
  181. "superuserUrl": f"http://{self.organization.slug}.testserver",
  182. }
  183. else:
  184. assert data["links"] == {
  185. "organizationUrl": None,
  186. "regionUrl": None,
  187. "sentryUrl": "http://testserver",
  188. }
  189. assert "activeorg" not in self.client.session
  190. # Induce last active organization
  191. with (
  192. override_settings(SENTRY_USE_CUSTOMER_DOMAINS=True),
  193. self.feature({"organizations:customer-domains": [other_org.slug]}),
  194. assume_test_silo_mode(SiloMode.MONOLITH),
  195. ):
  196. response = self.client.get(
  197. "/",
  198. HTTP_HOST=f"{other_org.slug}.testserver",
  199. follow=True,
  200. )
  201. assert response.status_code == 200
  202. if is_superuser:
  203. assert response.redirect_chain == [
  204. (f"http://{other_org.slug}.testserver/issues/", 302)
  205. ]
  206. assert self.client.session["activeorg"] == other_org.slug
  207. else:
  208. assert response.redirect_chain == [
  209. (f"http://{other_org.slug}.testserver/auth/login/{other_org.slug}/", 302)
  210. ]
  211. assert "activeorg" not in self.client.session
  212. # lastOrganization is set
  213. with mock.patch("sentry.auth.superuser.SUPERUSER_ORG_ID", self.organization.id):
  214. resp = self.client.get(self.path)
  215. assert resp.status_code == 200
  216. assert resp["Content-Type"] == "application/json"
  217. data = json.loads(resp.content)
  218. if is_superuser:
  219. assert data["lastOrganization"] == other_org.slug
  220. assert data["links"] == {
  221. "organizationUrl": f"http://{other_org.slug}.testserver",
  222. "regionUrl": generate_region_url(),
  223. "sentryUrl": "http://testserver",
  224. "superuserUrl": f"http://{self.organization.slug}.testserver",
  225. }
  226. else:
  227. assert data["lastOrganization"] is None
  228. assert data["links"] == {
  229. "organizationUrl": None,
  230. "regionUrl": None,
  231. "sentryUrl": "http://testserver",
  232. }
  233. def test_superuser(self):
  234. self._run_test_with_privileges(is_superuser=True, is_staff=False)
  235. @with_feature("auth:enterprise-staff-cookie")
  236. def test_staff(self):
  237. self._run_test_with_privileges(is_superuser=False, is_staff=True)
  238. @with_feature("auth:enterprise-staff-cookie")
  239. def test_superuser_and_staff(self):
  240. self._run_test_with_privileges(is_superuser=True, is_staff=True)
  241. def test_superuser_cookie_domain(self):
  242. # Cannot set the superuser cookie domain using override_settings().
  243. # So we set them and restore them manually.
  244. old_super_cookie_domain = superuser.COOKIE_DOMAIN
  245. superuser.COOKIE_DOMAIN = ".testserver"
  246. resp = self.client.get(self.path)
  247. assert resp.status_code == 200
  248. assert resp["Content-Type"] == "application/json"
  249. data = json.loads(resp.content)
  250. assert data["superUserCookieDomain"] == ".testserver"
  251. superuser.COOKIE_DOMAIN = None
  252. resp = self.client.get(self.path)
  253. assert resp.status_code == 200
  254. assert resp["Content-Type"] == "application/json"
  255. data = json.loads(resp.content)
  256. assert data["superUserCookieDomain"] is None
  257. # Restore values
  258. superuser.COOKIE_DOMAIN = old_super_cookie_domain
  259. def test_links_unauthenticated(self):
  260. resp = self.client.get(self.path)
  261. assert resp.status_code == 200
  262. assert resp["Content-Type"] == "application/json"
  263. data = json.loads(resp.content)
  264. assert not data["isAuthenticated"]
  265. assert data["user"] is None
  266. assert data["lastOrganization"] is None
  267. assert data["links"] == {
  268. "organizationUrl": None,
  269. "regionUrl": None,
  270. "sentryUrl": "http://testserver",
  271. }
  272. def test_links_authenticated(self):
  273. self.login_as(self.user)
  274. # Induce last active organization
  275. resp = self.client.get(
  276. reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
  277. )
  278. assert resp.status_code == 200
  279. assert resp["Content-Type"] == "application/json"
  280. resp = self.client.get(self.path)
  281. assert resp.status_code == 200
  282. assert resp["Content-Type"] == "application/json"
  283. data = json.loads(resp.content)
  284. assert data["isAuthenticated"] is True
  285. assert data["lastOrganization"] == self.organization.slug
  286. assert data["links"] == {
  287. "organizationUrl": f"http://{self.organization.slug}.testserver",
  288. "regionUrl": generate_region_url(),
  289. "sentryUrl": "http://testserver",
  290. }
  291. def test_organization_url_region(self):
  292. self.login_as(self.user)
  293. # Induce last active organization
  294. resp = self.client.get(
  295. reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
  296. )
  297. assert resp.status_code == 200
  298. assert resp["Content-Type"] == "application/json"
  299. with override_settings(SENTRY_REGION="eu"):
  300. resp = self.client.get(self.path)
  301. assert resp.status_code == 200
  302. assert resp["Content-Type"] == "application/json"
  303. data = json.loads(resp.content)
  304. assert data["isAuthenticated"] is True
  305. assert data["lastOrganization"] == self.organization.slug
  306. assert data["links"] == {
  307. "organizationUrl": f"http://{self.organization.slug}.testserver",
  308. "regionUrl": generate_region_url(),
  309. "sentryUrl": "http://testserver",
  310. }
  311. def test_organization_url_organization_base_hostname(self):
  312. self.login_as(self.user)
  313. # Induce last active organization
  314. resp = self.client.get(
  315. reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
  316. )
  317. assert resp.status_code == 200
  318. assert resp["Content-Type"] == "application/json"
  319. with self.options({"system.organization-base-hostname": "invalid"}):
  320. resp = self.client.get(self.path)
  321. assert resp.status_code == 200
  322. assert resp["Content-Type"] == "application/json"
  323. data = json.loads(resp.content)
  324. assert data["isAuthenticated"] is True
  325. assert data["lastOrganization"] == self.organization.slug
  326. assert data["links"] == {
  327. "organizationUrl": "http://testserver",
  328. "regionUrl": generate_region_url(),
  329. "sentryUrl": "http://testserver",
  330. }
  331. with self.options({"system.organization-base-hostname": "{slug}.testserver"}):
  332. resp = self.client.get(self.path)
  333. assert resp.status_code == 200
  334. assert resp["Content-Type"] == "application/json"
  335. data = json.loads(resp.content)
  336. assert data["isAuthenticated"] is True
  337. assert data["lastOrganization"] == self.organization.slug
  338. assert data["links"] == {
  339. "organizationUrl": f"http://{self.organization.slug}.testserver",
  340. "regionUrl": generate_region_url(),
  341. "sentryUrl": "http://testserver",
  342. }
  343. def test_organization_url_organization_url_template(self):
  344. self.login_as(self.user)
  345. # Induce last active organization
  346. resp = self.client.get(
  347. reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
  348. )
  349. assert resp.status_code == 200
  350. assert resp["Content-Type"] == "application/json"
  351. with self.options({"system.organization-url-template": "invalid"}):
  352. resp = self.client.get(self.path)
  353. assert resp.status_code == 200
  354. assert resp["Content-Type"] == "application/json"
  355. data = json.loads(resp.content)
  356. assert data["isAuthenticated"] is True
  357. assert data["lastOrganization"] == self.organization.slug
  358. assert data["links"] == {
  359. "organizationUrl": "invalid",
  360. "regionUrl": generate_region_url(),
  361. "sentryUrl": "http://testserver",
  362. }
  363. with self.options({"system.organization-url-template": None}):
  364. resp = self.client.get(self.path)
  365. assert resp.status_code == 200
  366. assert resp["Content-Type"] == "application/json"
  367. data = json.loads(resp.content)
  368. assert data["isAuthenticated"] is True
  369. assert data["lastOrganization"] == self.organization.slug
  370. assert data["links"] == {
  371. "organizationUrl": "http://testserver",
  372. "regionUrl": generate_region_url(),
  373. "sentryUrl": "http://testserver",
  374. }
  375. with self.options({"system.organization-url-template": "ftp://{hostname}"}):
  376. resp = self.client.get(self.path)
  377. assert resp.status_code == 200
  378. assert resp["Content-Type"] == "application/json"
  379. data = json.loads(resp.content)
  380. assert data["isAuthenticated"] is True
  381. assert data["lastOrganization"] == self.organization.slug
  382. assert data["links"] == {
  383. "organizationUrl": f"ftp://{self.organization.slug}.testserver",
  384. "regionUrl": generate_region_url(),
  385. "sentryUrl": "http://testserver",
  386. }
  387. def test_deleted_last_organization(self):
  388. self.login_as(self.user)
  389. # Check lastOrganization
  390. resp = self.client.get(self.path)
  391. assert resp.status_code == 200
  392. assert resp["Content-Type"] == "application/json"
  393. data = json.loads(resp.content)
  394. assert data["isAuthenticated"] is True
  395. assert data["lastOrganization"] is None
  396. assert "activeorg" not in self.client.session
  397. # Induce last active organization
  398. resp = self.client.get(
  399. reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
  400. )
  401. assert resp.status_code == 200
  402. assert resp["Content-Type"] == "application/json"
  403. # Check lastOrganization
  404. resp = self.client.get(self.path)
  405. assert resp.status_code == 200
  406. assert resp["Content-Type"] == "application/json"
  407. data = json.loads(resp.content)
  408. assert data["isAuthenticated"] is True
  409. assert data["lastOrganization"] == self.organization.slug
  410. assert self.client.session["activeorg"] == self.organization.slug
  411. # Delete lastOrganization
  412. assert Organization.objects.filter(slug=self.organization.slug).count() == 1
  413. assert RegionScheduledDeletion.objects.count() == 0
  414. self.organization.update(status=OrganizationStatus.PENDING_DELETION)
  415. deletion = RegionScheduledDeletion.schedule(self.organization, days=0)
  416. deletion.update(in_progress=True)
  417. with self.tasks():
  418. run_deletion(deletion.id)
  419. assert Organization.objects.filter(slug=self.organization.slug).count() == 0
  420. # Check lastOrganization
  421. resp = self.client.get(self.path)
  422. assert resp.status_code == 200
  423. assert resp["Content-Type"] == "application/json"
  424. data = json.loads(resp.content)
  425. assert data["isAuthenticated"] is True
  426. assert data["lastOrganization"] is None
  427. assert "activeorg" not in self.client.session
  428. def test_not_member_of_last_org(self):
  429. self.login_as(self.user)
  430. other_org = self.create_organization(
  431. name="other_org", owner=self.create_user("bar@example.com")
  432. )
  433. member_om = self.create_member(user=self.user, organization=other_org, role="owner")
  434. # Check lastOrganization
  435. resp = self.client.get(self.path)
  436. assert resp.status_code == 200
  437. assert resp["Content-Type"] == "application/json"
  438. data = json.loads(resp.content)
  439. assert data["isAuthenticated"] is True
  440. assert data["lastOrganization"] is None
  441. assert "activeorg" not in self.client.session
  442. # Induce last active organization
  443. resp = self.client.get(reverse("sentry-api-0-organization-projects", args=[other_org.slug]))
  444. assert resp.status_code == 200
  445. assert resp["Content-Type"] == "application/json"
  446. # Check lastOrganization
  447. resp = self.client.get(self.path)
  448. assert resp.status_code == 200
  449. assert resp["Content-Type"] == "application/json"
  450. data = json.loads(resp.content)
  451. assert data["isAuthenticated"] is True
  452. assert data["lastOrganization"] == other_org.slug
  453. assert self.client.session["activeorg"] == other_org.slug
  454. # Delete membership
  455. assert OrganizationMember.objects.filter(id=member_om.id).exists()
  456. resp = self.client.delete(
  457. reverse("sentry-api-0-organization-member-details", args=[other_org.slug, member_om.id])
  458. )
  459. assert resp.status_code == 204
  460. assert not OrganizationMember.objects.filter(id=member_om.id).exists()
  461. # Check lastOrganization
  462. resp = self.client.get(self.path)
  463. assert resp.status_code == 200
  464. assert resp["Content-Type"] == "application/json"
  465. data = json.loads(resp.content)
  466. assert data["isAuthenticated"] is True
  467. assert data["lastOrganization"] is None
  468. assert "activeorg" not in self.client.session
  469. def test_api_token(self):
  470. with assume_test_silo_mode(SiloMode.CONTROL):
  471. api_token = ApiToken.objects.create(
  472. user=self.user, scope_list=["org:write", "org:read"]
  473. )
  474. HTTP_AUTHORIZATION = f"Bearer {api_token.token}"
  475. # Induce last active organization
  476. resp = self.client.get(
  477. reverse("sentry-api-0-organization-projects", args=[self.organization.slug]),
  478. HTTP_AUTHORIZATION=HTTP_AUTHORIZATION,
  479. )
  480. assert resp.status_code == 200
  481. assert resp["Content-Type"] == "application/json"
  482. assert "activeorg" not in self.client.session
  483. # Load client config
  484. response = self.client.get(self.path, HTTP_AUTHORIZATION=HTTP_AUTHORIZATION)
  485. assert response.status_code == 200
  486. assert response["Content-Type"] == "application/json"
  487. data = json.loads(response.content)
  488. assert data["isAuthenticated"] is True
  489. assert data["lastOrganization"] is None
  490. assert data["links"] == {
  491. "organizationUrl": None,
  492. "regionUrl": None,
  493. "sentryUrl": "http://testserver",
  494. }
  495. def test_region_api_url_template(self):
  496. self.login_as(self.user)
  497. # Induce last active organization
  498. resp = self.client.get(
  499. reverse("sentry-api-0-organization-projects", args=[self.organization.slug])
  500. )
  501. assert resp.status_code == 200
  502. assert resp["Content-Type"] == "application/json"
  503. with self.options({"system.region-api-url-template": "http://foobar.{region}.testserver"}):
  504. resp = self.client.get(self.path)
  505. assert resp.status_code == 200
  506. assert resp["Content-Type"] == "application/json"
  507. data = json.loads(resp.content)
  508. expected_region_url = (
  509. "http://foobar.us.testserver"
  510. if SiloMode.get_current_mode() == SiloMode.REGION
  511. else options.get("system.url-prefix")
  512. )
  513. assert data["isAuthenticated"] is True
  514. assert data["lastOrganization"] == self.organization.slug
  515. assert data["links"] == {
  516. "organizationUrl": f"http://{self.organization.slug}.testserver",
  517. "regionUrl": expected_region_url,
  518. "sentryUrl": "http://testserver",
  519. }
  520. def test_customer_domain(self):
  521. # With customer domain
  522. resp = self.client.get(self.path, HTTP_HOST="albertos-apples.testserver")
  523. assert resp.status_code == 200
  524. assert resp["Content-Type"] == "application/json"
  525. data = json.loads(resp.content)
  526. assert not data["isAuthenticated"]
  527. assert data["customerDomain"] == {
  528. "organizationUrl": "http://albertos-apples.testserver",
  529. "sentryUrl": "http://testserver",
  530. "subdomain": "albertos-apples",
  531. }
  532. # Without customer domain
  533. resp = self.client.get(self.path, HTTP_HOST="testserver")
  534. assert resp.status_code == 200
  535. assert resp["Content-Type"] == "application/json"
  536. data = json.loads(resp.content)
  537. assert not data["isAuthenticated"]
  538. assert data["customerDomain"] is None