index.spec.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {SecretFixture} from 'sentry-fixture/secret';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {
  5. render,
  6. renderGlobalModal,
  7. screen,
  8. userEvent,
  9. waitForElementToBeRemoved,
  10. } from 'sentry-test/reactTestingLibrary';
  11. import * as indicators from 'sentry/actionCreators/indicator';
  12. import OrganizationsStore from 'sentry/stores/organizationsStore';
  13. import {
  14. OrganizationFeatureFlagsIndex,
  15. type Secret,
  16. } from 'sentry/views/settings/featureFlags';
  17. describe('OrganizationFeatureFlagsIndex', function () {
  18. const ENDPOINT = '/organizations/org-slug/flags/signing-secrets/';
  19. const {organization} = initializeOrg();
  20. beforeEach(function () {
  21. MockApiClient.addMockResponse({
  22. url: '/organizations/org-slug/users/1234/',
  23. body: {},
  24. });
  25. OrganizationsStore.addOrReplace(organization);
  26. });
  27. afterEach(function () {
  28. MockApiClient.clearMockResponses();
  29. });
  30. it('shows secrets', async function () {
  31. const secrets: Secret[] = [
  32. SecretFixture(),
  33. SecretFixture({id: 2, provider: 'openfeature', secret: '456def****'}),
  34. ];
  35. const mock = MockApiClient.addMockResponse({
  36. url: ENDPOINT,
  37. method: 'GET',
  38. body: {data: secrets},
  39. });
  40. render(<OrganizationFeatureFlagsIndex />);
  41. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  42. // Then list
  43. expect(screen.getByText('launchdarkly')).toBeInTheDocument();
  44. expect(screen.getByText('openfeature')).toBeInTheDocument();
  45. expect(screen.queryByTestId('loading-error')).not.toBeInTheDocument();
  46. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  47. expect(screen.queryByTestId('empty-state')).not.toBeInTheDocument();
  48. expect(mock).toHaveBeenCalledTimes(1);
  49. expect(mock).toHaveBeenCalledWith(ENDPOINT, expect.objectContaining({method: 'GET'}));
  50. });
  51. it('handle error when loading secrets', async function () {
  52. const mock = MockApiClient.addMockResponse({
  53. url: ENDPOINT,
  54. method: 'GET',
  55. statusCode: 400,
  56. });
  57. render(<OrganizationFeatureFlagsIndex />);
  58. expect(await screen.findByTestId('loading-error')).toHaveTextContent(
  59. 'Failed to load secrets and providers for the organization.'
  60. );
  61. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  62. expect(screen.queryByTestId('empty-state')).not.toBeInTheDocument();
  63. expect(mock).toHaveBeenCalledTimes(1);
  64. });
  65. it('shows empty state', async function () {
  66. const secrets: Secret[] = [];
  67. MockApiClient.addMockResponse({
  68. url: ENDPOINT,
  69. method: 'GET',
  70. body: {data: secrets},
  71. });
  72. render(<OrganizationFeatureFlagsIndex />);
  73. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  74. expect(screen.getByTestId('empty-state')).toBeInTheDocument();
  75. expect(screen.queryByTestId('loading-error')).not.toBeInTheDocument();
  76. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  77. });
  78. describe('removing', function () {
  79. it('allows to remove secrets', async function () {
  80. jest.spyOn(indicators, 'addSuccessMessage');
  81. const secrets: Secret[] = [
  82. SecretFixture(),
  83. SecretFixture({id: 2, provider: 'openfeature', secret: '456def****'}),
  84. ];
  85. MockApiClient.addMockResponse({
  86. url: ENDPOINT,
  87. method: 'GET',
  88. body: {data: secrets},
  89. });
  90. const deleteMock = MockApiClient.addMockResponse({
  91. url: `${ENDPOINT}1/`,
  92. method: 'DELETE',
  93. });
  94. render(<OrganizationFeatureFlagsIndex />);
  95. renderGlobalModal();
  96. expect(await screen.findByText('openfeature')).toBeInTheDocument();
  97. expect(screen.getByText('launchdarkly')).toBeInTheDocument();
  98. expect(
  99. screen.getByLabelText('Remove secret for launchdarkly provider')
  100. ).toBeEnabled();
  101. await userEvent.click(
  102. screen.getByLabelText('Remove secret for launchdarkly provider')
  103. );
  104. // Confirm modal
  105. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  106. expect(screen.getByText('openfeature')).toBeInTheDocument();
  107. expect(screen.queryByText('launchdarkly')).not.toBeInTheDocument();
  108. expect(indicators.addSuccessMessage).toHaveBeenCalledWith(
  109. 'Removed the provider and signing secret for the organization.'
  110. );
  111. expect(deleteMock).toHaveBeenCalledTimes(1);
  112. });
  113. it('does not allow to remove without permission', async function () {
  114. const org = OrganizationFixture({
  115. access: ['org:integrations'],
  116. });
  117. const secrets: Secret[] = [
  118. SecretFixture(),
  119. SecretFixture({
  120. id: 2,
  121. provider: 'openfeature',
  122. secret: '456def**************************',
  123. }),
  124. ];
  125. MockApiClient.addMockResponse({
  126. url: ENDPOINT,
  127. method: 'GET',
  128. body: {data: secrets},
  129. });
  130. render(<OrganizationFeatureFlagsIndex />, {organization: org});
  131. expect(await screen.findByText('launchdarkly')).toBeInTheDocument();
  132. expect(
  133. screen.getByLabelText('Remove secret for launchdarkly provider')
  134. ).toBeDisabled();
  135. });
  136. });
  137. });