index.spec.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import {BillingDetailsFixture} from 'getsentry-test/fixtures/billingDetails';
  2. import {InvoiceFixture} from 'getsentry-test/fixtures/invoice';
  3. import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
  4. import {initializeOrg} from 'sentry-test/initializeOrg';
  5. import {
  6. render,
  7. renderGlobalModal,
  8. screen,
  9. userEvent,
  10. waitFor,
  11. } from 'sentry-test/reactTestingLibrary';
  12. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  13. import {InvoiceItemType} from 'getsentry/types';
  14. import InvoiceDetails from 'getsentry/views/invoiceDetails';
  15. describe('InvoiceDetails', function () {
  16. const {organization, router, routerProps} = initializeOrg();
  17. const basicInvoice = InvoiceFixture(
  18. {
  19. dateCreated: '2021-09-20T22:33:38.042Z',
  20. items: [
  21. {
  22. type: InvoiceItemType.SUBSCRIPTION,
  23. description: 'Subscription to Business',
  24. amount: 8900,
  25. periodEnd: '2021-10-21',
  26. periodStart: '2021-09-21',
  27. data: {},
  28. },
  29. ],
  30. },
  31. organization
  32. );
  33. const creditInvoice = InvoiceFixture(
  34. {
  35. amount: 8900,
  36. amountBilled: 8400,
  37. creditApplied: 500,
  38. items: [
  39. {
  40. type: InvoiceItemType.SUBSCRIPTION,
  41. description: 'Subscription to Business',
  42. amount: 8900,
  43. periodEnd: '2021-10-21',
  44. periodStart: '2021-09-21',
  45. data: {},
  46. },
  47. {
  48. type: InvoiceItemType.CREDIT_APPLIED,
  49. description: 'Credit applied',
  50. amount: 500,
  51. periodEnd: '2021-10-21',
  52. periodStart: '2021-09-21',
  53. data: {},
  54. },
  55. ],
  56. },
  57. organization
  58. );
  59. const params = {invoiceGuid: basicInvoice.id};
  60. beforeEach(function () {
  61. MockApiClient.clearMockResponses();
  62. SubscriptionStore.set(organization.slug, {});
  63. MockApiClient.addMockResponse({
  64. url: `/customers/${organization.slug}/billing-details/`,
  65. method: 'GET',
  66. body: {},
  67. });
  68. });
  69. it('renders basic invoice details', async function () {
  70. const mockapi = MockApiClient.addMockResponse({
  71. url: `/customers/${organization.slug}/invoices/${basicInvoice.id}/`,
  72. method: 'GET',
  73. body: basicInvoice,
  74. });
  75. render(
  76. <InvoiceDetails {...routerProps} params={params} organization={organization} />
  77. );
  78. await waitFor(() => expect(mockapi).toHaveBeenCalled());
  79. expect(await screen.findByText('Sentry')).toBeInTheDocument();
  80. expect(screen.getByText(/Subscription to Business/)).toBeInTheDocument();
  81. expect(screen.getByText('Sep 21, 2021')).toBeInTheDocument();
  82. expect(screen.getByText('Oct 21, 2021')).toBeInTheDocument();
  83. expect(screen.getByText('Sep 20, 2021')).toBeInTheDocument();
  84. expect(screen.getByText('$89.00 USD')).toBeInTheDocument();
  85. });
  86. it('renders credit applied', async function () {
  87. const mockapi = MockApiClient.addMockResponse({
  88. url: `/customers/${organization.slug}/invoices/${creditInvoice.id}/`,
  89. method: 'GET',
  90. body: creditInvoice,
  91. });
  92. const creditParams = {invoiceGuid: creditInvoice.id};
  93. render(
  94. <InvoiceDetails
  95. {...routerProps}
  96. params={creditParams}
  97. organization={organization}
  98. />
  99. );
  100. await waitFor(() => expect(mockapi).toHaveBeenCalled());
  101. expect(await screen.findByText('Sentry')).toBeInTheDocument();
  102. expect(screen.getByText(/Subscription to Business/)).toBeInTheDocument();
  103. expect(screen.getByText('$89.00 USD')).toBeInTheDocument();
  104. });
  105. it('renders an error', async function () {
  106. const mockapi = MockApiClient.addMockResponse({
  107. url: `/customers/${organization.slug}/invoices/${basicInvoice.id}/`,
  108. method: 'GET',
  109. statusCode: 404,
  110. body: {},
  111. });
  112. render(<InvoiceDetails {...routerProps} params={params} />);
  113. await waitFor(() => expect(mockapi).toHaveBeenCalled());
  114. expect(
  115. await screen.findByText('There was an error loading data.')
  116. ).toBeInTheDocument();
  117. });
  118. it('sends a request to email the invoice', async function () {
  119. const mockget = MockApiClient.addMockResponse({
  120. url: `/customers/${organization.slug}/invoices/${basicInvoice.id}/`,
  121. method: 'GET',
  122. statusCode: 200,
  123. body: basicInvoice,
  124. });
  125. const mockpost = MockApiClient.addMockResponse({
  126. url: `/customers/${organization.slug}/invoices/${basicInvoice.id}/`,
  127. method: 'POST',
  128. });
  129. render(<InvoiceDetails {...routerProps} params={params} />);
  130. await waitFor(() => expect(mockget).toHaveBeenCalled());
  131. const input = await screen.findByPlaceholderText('you@example.com');
  132. await userEvent.type(input, 'user@example.com');
  133. const button = screen.getByText('Email Receipt');
  134. await userEvent.click(button);
  135. await waitFor(() => expect(mockpost).toHaveBeenCalled());
  136. expect(mockpost).toHaveBeenCalledWith(
  137. `/customers/${organization.slug}/invoices/${basicInvoice.id}/`,
  138. expect.objectContaining({
  139. data: {op: 'send_receipt', email: 'user@example.com'},
  140. })
  141. );
  142. // Form should be reset.
  143. expect(screen.queryByText('user@example.com')).not.toBeInTheDocument();
  144. });
  145. it('renders with open pay now with billing failure referrer', async function () {
  146. router.location = {
  147. ...router.location,
  148. query: {referrer: 'billing-failure'},
  149. };
  150. const pastDueInvoice = InvoiceFixture(
  151. {
  152. amount: 8900,
  153. isClosed: false,
  154. isPaid: false,
  155. items: [
  156. {
  157. type: InvoiceItemType.SUBSCRIPTION,
  158. description: 'Subscription to Business',
  159. amount: 8900,
  160. periodEnd: '2021-10-21',
  161. periodStart: '2021-09-21',
  162. data: {},
  163. },
  164. ],
  165. },
  166. organization
  167. );
  168. const pastDueParams = {invoiceGuid: pastDueInvoice.id};
  169. const mockapiInvoice = MockApiClient.addMockResponse({
  170. url: `/customers/${organization.slug}/invoices/${pastDueInvoice.id}/`,
  171. method: 'GET',
  172. body: pastDueInvoice,
  173. });
  174. const mockapiPayments = MockApiClient.addMockResponse({
  175. url: `/organizations/${organization.slug}/payments/${pastDueInvoice.id}/new/`,
  176. method: 'GET',
  177. body: {},
  178. });
  179. renderGlobalModal();
  180. render(
  181. <InvoiceDetails
  182. {...routerProps}
  183. params={pastDueParams}
  184. organization={organization}
  185. />,
  186. {
  187. router,
  188. }
  189. );
  190. await waitFor(() => expect(mockapiInvoice).toHaveBeenCalled());
  191. await waitFor(() => expect(mockapiPayments).toHaveBeenCalled());
  192. expect(screen.getByText(/Invoice Details/)).toBeInTheDocument();
  193. expect(screen.getAllByText(/Pay Now/)).toHaveLength(2);
  194. expect(screen.getByText(/Pay Invoice/)).toBeInTheDocument();
  195. expect(screen.getByText(/Card Details/)).toBeInTheDocument();
  196. expect(screen.getByTestId('modal-backdrop')).toBeInTheDocument();
  197. expect(screen.getByTestId('cancel')).toBeInTheDocument();
  198. expect(screen.getByTestId('submit')).toBeInTheDocument();
  199. });
  200. describe('Invoice Details Attributes', function () {
  201. const billingDetails = BillingDetailsFixture({taxNumber: '123456789'});
  202. SubscriptionFixture({organization});
  203. beforeEach(function () {
  204. MockApiClient.clearMockResponses();
  205. SubscriptionStore.set(organization.slug, {});
  206. MockApiClient.addMockResponse({
  207. url: `/customers/${organization.slug}/billing-details/`,
  208. method: 'GET',
  209. body: billingDetails,
  210. });
  211. });
  212. it('renders with billing address', async function () {
  213. const mockInvoice = MockApiClient.addMockResponse({
  214. url: `/customers/${organization.slug}/invoices/${basicInvoice.id}/`,
  215. method: 'GET',
  216. body: basicInvoice,
  217. });
  218. render(<InvoiceDetails {...routerProps} params={params} />);
  219. await waitFor(() => expect(mockInvoice).toHaveBeenCalled());
  220. expect(
  221. await screen.findByText(`${billingDetails.companyName}`)
  222. ).toBeInTheDocument();
  223. expect(screen.getByText('Details:')).toBeInTheDocument();
  224. expect(screen.getByText(`${billingDetails.displayAddress}`)).toBeInTheDocument();
  225. expect(screen.getByText('Tax Number:')).toBeInTheDocument();
  226. expect(screen.getByText(`${billingDetails.taxNumber}`)).toBeInTheDocument();
  227. expect(screen.getByText(`${billingDetails.billingEmail}`)).toBeInTheDocument();
  228. expect(screen.queryByText('Country Id: 1234')).not.toBeInTheDocument();
  229. expect(screen.queryByText('Regional Tax Id: 5678')).not.toBeInTheDocument();
  230. });
  231. it('renders sentry tax ids', async function () {
  232. const basicInvoiceWithSentryTaxIds = InvoiceFixture(
  233. {
  234. sentryTaxIds: {
  235. taxId: '1234',
  236. taxIdName: 'Country Id',
  237. region: {
  238. code: 'AA',
  239. taxId: '5678',
  240. taxIdName: 'Regional Tax Id',
  241. },
  242. },
  243. },
  244. organization
  245. );
  246. const mockInvoice = MockApiClient.addMockResponse({
  247. url: `/customers/${organization.slug}/invoices/${basicInvoiceWithSentryTaxIds.id}/`,
  248. method: 'GET',
  249. body: basicInvoiceWithSentryTaxIds,
  250. });
  251. render(<InvoiceDetails {...routerProps} params={params} />);
  252. await waitFor(() => expect(mockInvoice).toHaveBeenCalled());
  253. expect(await screen.findByText('Country Id: 1234')).toBeInTheDocument();
  254. expect(screen.getByText('Regional Tax Id: 5678')).toBeInTheDocument();
  255. });
  256. it('renders reverse charge row', async function () {
  257. const basicInvoiceReverseCharge = InvoiceFixture(
  258. {
  259. isReverseCharge: true,
  260. defaultTaxName: 'VAT',
  261. },
  262. organization
  263. );
  264. const mockInvoice = MockApiClient.addMockResponse({
  265. url: `/customers/${organization.slug}/invoices/${basicInvoiceReverseCharge.id}/`,
  266. method: 'GET',
  267. body: basicInvoiceReverseCharge,
  268. });
  269. render(<InvoiceDetails {...routerProps} params={params} />);
  270. await waitFor(() => expect(mockInvoice).toHaveBeenCalled());
  271. expect(await screen.findByText('VAT')).toBeInTheDocument();
  272. expect(screen.getByText('Reverse Charge')).toBeInTheDocument();
  273. });
  274. });
  275. });