setBudgetAndReserves.spec.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture';
  3. import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig';
  4. import {MetricHistoryFixture} from 'getsentry-test/fixtures/metricHistory';
  5. import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
  6. import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  7. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  8. import {OnDemandBudgetMode, PlanTier} from 'getsentry/types';
  9. import AMCheckout from 'getsentry/views/amCheckout';
  10. describe('SetBudgetAndReserves', function () {
  11. const api = new MockApiClient();
  12. const organization = OrganizationFixture({
  13. features: ['ondemand-budgets', 'am3-billing'],
  14. });
  15. const params = {};
  16. const stepBody =
  17. /This budget ensures continued monitoring after you've used up your reserved event volume/;
  18. async function openPanel(planChoice: 'Business' | 'Team' = 'Business') {
  19. expect(await screen.findByTestId('header-choose-your-plan')).toBeInTheDocument();
  20. const selectedRadio = screen.getByRole('radio', {name: planChoice});
  21. await userEvent.click(selectedRadio);
  22. expect(selectedRadio).toBeChecked();
  23. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  24. }
  25. beforeEach(function () {
  26. MockApiClient.clearMockResponses();
  27. MockApiClient.addMockResponse({
  28. url: `/subscriptions/${organization.slug}/`,
  29. method: 'GET',
  30. body: {},
  31. });
  32. MockApiClient.addMockResponse({
  33. url: `/customers/${organization.slug}/billing-config/`,
  34. method: 'GET',
  35. body: BillingConfigFixture(PlanTier.AM3),
  36. });
  37. MockApiClient.addMockResponse({
  38. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  39. method: 'POST',
  40. });
  41. MockApiClient.addMockResponse({
  42. url: `/customers/${organization.slug}/plan-migrations/?applied=0`,
  43. method: 'GET',
  44. body: [],
  45. });
  46. });
  47. it('renders with business plan and default PAYG budget by default for new customers', async function () {
  48. const sub = SubscriptionFixture({
  49. organization,
  50. plan: 'am3_f',
  51. planTier: PlanTier.AM3,
  52. });
  53. act(() => SubscriptionStore.set(organization.slug, sub));
  54. render(
  55. <AMCheckout
  56. {...RouteComponentPropsFixture()}
  57. params={params}
  58. api={api}
  59. organization={organization}
  60. checkoutTier={PlanTier.AM3}
  61. onToggleLegacy={jest.fn()}
  62. />
  63. );
  64. await openPanel();
  65. expect(screen.getByText(stepBody)).toBeInTheDocument();
  66. expect(screen.getByRole('textbox', {name: 'Pay-as-you-go budget'})).toHaveValue(
  67. '300'
  68. );
  69. });
  70. it('renders with team plan and default PAYG budget by default for new customers', async function () {
  71. const sub = SubscriptionFixture({
  72. organization,
  73. plan: 'am3_f',
  74. planTier: PlanTier.AM3,
  75. });
  76. act(() => SubscriptionStore.set(organization.slug, sub));
  77. render(
  78. <AMCheckout
  79. {...RouteComponentPropsFixture()}
  80. params={params}
  81. api={api}
  82. organization={organization}
  83. checkoutTier={PlanTier.AM3}
  84. onToggleLegacy={jest.fn()}
  85. />
  86. );
  87. await openPanel('Team');
  88. expect(screen.getByText(stepBody)).toBeInTheDocument();
  89. expect(screen.getByRole('textbox', {name: 'Pay-as-you-go budget'})).toHaveValue(
  90. '100'
  91. );
  92. });
  93. it('renders with existing PAYG budget and reserved volumes', async function () {
  94. const sub = SubscriptionFixture({
  95. organization,
  96. plan: 'am3_team',
  97. planTier: PlanTier.AM3,
  98. categories: {
  99. errors: MetricHistoryFixture({reserved: 100_000}),
  100. attachments: MetricHistoryFixture({reserved: 25}),
  101. replays: MetricHistoryFixture({reserved: 50}),
  102. monitorSeats: MetricHistoryFixture({reserved: 1}),
  103. spans: MetricHistoryFixture({reserved: 10_000_000}),
  104. profileDuration: MetricHistoryFixture({reserved: 1}),
  105. },
  106. onDemandMaxSpend: 30_00,
  107. onDemandBudgets: {
  108. budgetMode: OnDemandBudgetMode.SHARED,
  109. sharedMaxBudget: 30_00,
  110. enabled: true,
  111. onDemandSpendUsed: 0,
  112. },
  113. });
  114. act(() => SubscriptionStore.set(organization.slug, sub));
  115. render(
  116. <AMCheckout
  117. {...RouteComponentPropsFixture()}
  118. params={params}
  119. api={api}
  120. organization={organization}
  121. checkoutTier={PlanTier.AM3}
  122. onToggleLegacy={jest.fn()}
  123. />
  124. );
  125. await openPanel();
  126. expect(screen.getByText(stepBody)).toBeInTheDocument();
  127. expect(screen.getByRole('textbox', {name: 'Pay-as-you-go budget'})).toHaveValue('30');
  128. expect(screen.getByRole('slider', {name: 'Errors'})).toHaveAttribute(
  129. 'aria-valuetext',
  130. '100000'
  131. );
  132. expect(screen.getByRole('slider', {name: 'Replays'})).toHaveAttribute(
  133. 'aria-valuetext',
  134. '50'
  135. );
  136. expect(screen.getByRole('slider', {name: 'Spans'})).toHaveAttribute(
  137. 'aria-valuetext',
  138. '10000000'
  139. );
  140. expect(screen.getByRole('slider', {name: 'Attachments'})).toHaveAttribute(
  141. 'aria-valuetext',
  142. '25'
  143. );
  144. });
  145. it('can complete step', async function () {
  146. const sub = SubscriptionFixture({
  147. organization,
  148. plan: 'am3_f',
  149. planTier: PlanTier.AM3,
  150. });
  151. act(() => SubscriptionStore.set(organization.slug, sub));
  152. render(
  153. <AMCheckout
  154. {...RouteComponentPropsFixture()}
  155. params={params}
  156. api={api}
  157. organization={organization}
  158. checkoutTier={PlanTier.AM3}
  159. onToggleLegacy={jest.fn()}
  160. />
  161. );
  162. await openPanel();
  163. expect(screen.getByText(stepBody)).toBeInTheDocument();
  164. // continue to close
  165. await userEvent.click(screen.getByRole('button', {name: 'Continue'}));
  166. expect(screen.queryByText(stepBody)).not.toBeInTheDocument();
  167. expect(
  168. screen.queryByRole('textbox', {name: 'Pay-as-you-go budget'})
  169. ).not.toBeInTheDocument();
  170. });
  171. it('renders with closest plan and default PAYG budget by default for customers migrating from partner billing', async function () {
  172. organization.features.push('partner-billing-migration');
  173. const sub = SubscriptionFixture({
  174. organization,
  175. plan: 'am2_sponsored_team_auf',
  176. planTier: PlanTier.AM2,
  177. partner: {
  178. isActive: true,
  179. externalId: 'yuh',
  180. partnership: {
  181. id: 'FOO',
  182. displayName: 'FOO',
  183. supportNote: '',
  184. },
  185. name: '',
  186. },
  187. });
  188. act(() => SubscriptionStore.set(organization.slug, sub));
  189. render(
  190. <AMCheckout
  191. {...RouteComponentPropsFixture()}
  192. params={params}
  193. api={api}
  194. organization={organization}
  195. checkoutTier={PlanTier.AM3}
  196. onToggleLegacy={jest.fn()}
  197. />
  198. );
  199. await openPanel('Team');
  200. expect(screen.getByText(stepBody)).toBeInTheDocument();
  201. expect(screen.getByRole('textbox', {name: 'Pay-as-you-go budget'})).toHaveValue(
  202. '100'
  203. );
  204. });
  205. it('omits monitor seats, stored spans, and profile duration from volume sliders', async function () {
  206. const sub = SubscriptionFixture({
  207. organization,
  208. plan: 'am3_team',
  209. planTier: PlanTier.AM3,
  210. categories: {
  211. errors: MetricHistoryFixture({reserved: 100_000}),
  212. attachments: MetricHistoryFixture({reserved: 25}),
  213. replays: MetricHistoryFixture({reserved: 50}),
  214. monitorSeats: MetricHistoryFixture({reserved: 1}),
  215. spans: MetricHistoryFixture({reserved: 1}),
  216. profileDuration: MetricHistoryFixture({reserved: 1}),
  217. },
  218. onDemandMaxSpend: 30_00,
  219. onDemandBudgets: {
  220. budgetMode: OnDemandBudgetMode.SHARED,
  221. sharedMaxBudget: 30_00,
  222. enabled: true,
  223. onDemandSpendUsed: 0,
  224. },
  225. });
  226. act(() => SubscriptionStore.set(organization.slug, sub));
  227. render(
  228. <AMCheckout
  229. {...RouteComponentPropsFixture()}
  230. params={params}
  231. api={api}
  232. organization={organization}
  233. checkoutTier={PlanTier.AM3}
  234. onToggleLegacy={jest.fn()}
  235. />
  236. );
  237. await openPanel();
  238. // Verify monitor seats slider is not rendered
  239. expect(screen.queryByRole('slider', {name: 'Cron Monitors'})).not.toBeInTheDocument();
  240. expect(screen.queryByTestId('monitorSeats-volume-item')).not.toBeInTheDocument();
  241. // Verify stored spans slider is not rendered
  242. expect(screen.queryByRole('slider', {name: 'Stored Spans'})).not.toBeInTheDocument();
  243. expect(screen.queryByTestId('spansIndexed-volume-item')).not.toBeInTheDocument();
  244. // Verify profile duration slider is not rendered
  245. expect(
  246. screen.queryByRole('slider', {name: 'Profile Duration'})
  247. ).not.toBeInTheDocument();
  248. expect(screen.queryByTestId('profile-duration-volume-item')).not.toBeInTheDocument();
  249. // Verify accepted spans slider is rendered but without 'Accepted' in the label
  250. expect(
  251. screen.queryByRole('slider', {name: 'Accepted Spans'})
  252. ).not.toBeInTheDocument();
  253. expect(screen.getByRole('slider', {name: 'Spans'})).toBeInTheDocument();
  254. // Verify other sliders are still rendered
  255. expect(screen.getByRole('slider', {name: 'Errors'})).toBeInTheDocument();
  256. expect(screen.getByRole('slider', {name: 'Replays'})).toBeInTheDocument();
  257. expect(screen.getByRole('slider', {name: 'Attachments'})).toBeInTheDocument();
  258. });
  259. });