index.spec.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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. expect(screen.getByText('launchdarkly')).toBeInTheDocument();
  43. expect(screen.getByText('openfeature')).toBeInTheDocument();
  44. expect(screen.queryByTestId('loading-error')).not.toBeInTheDocument();
  45. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  46. expect(screen.queryByTestId('empty-state')).not.toBeInTheDocument();
  47. expect(mock).toHaveBeenCalledTimes(1);
  48. expect(mock).toHaveBeenCalledWith(ENDPOINT, expect.objectContaining({method: 'GET'}));
  49. });
  50. it('handle error when loading secrets', async function () {
  51. const mock = MockApiClient.addMockResponse({
  52. url: ENDPOINT,
  53. method: 'GET',
  54. statusCode: 400,
  55. });
  56. render(<OrganizationFeatureFlagsIndex />);
  57. expect(await screen.findByTestId('loading-error')).toHaveTextContent(
  58. 'Failed to load secrets and providers for the organization.'
  59. );
  60. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  61. expect(screen.queryByTestId('empty-state')).not.toBeInTheDocument();
  62. expect(mock).toHaveBeenCalledTimes(1);
  63. });
  64. it('shows empty state', async function () {
  65. const secrets: Secret[] = [];
  66. MockApiClient.addMockResponse({
  67. url: ENDPOINT,
  68. method: 'GET',
  69. body: {data: secrets},
  70. });
  71. render(<OrganizationFeatureFlagsIndex />);
  72. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  73. expect(screen.getByTestId('empty-state')).toBeInTheDocument();
  74. expect(screen.queryByTestId('loading-error')).not.toBeInTheDocument();
  75. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  76. });
  77. describe('removing', function () {
  78. it('allows to remove secrets', async function () {
  79. jest.spyOn(indicators, 'addSuccessMessage');
  80. const secrets: Secret[] = [
  81. SecretFixture(),
  82. SecretFixture({id: 2, provider: 'openfeature', secret: '456def****'}),
  83. ];
  84. MockApiClient.addMockResponse({
  85. url: ENDPOINT,
  86. method: 'GET',
  87. body: {data: secrets},
  88. });
  89. const deleteMock = MockApiClient.addMockResponse({
  90. url: `${ENDPOINT}1/`,
  91. method: 'DELETE',
  92. });
  93. render(<OrganizationFeatureFlagsIndex />);
  94. renderGlobalModal();
  95. expect(await screen.findByText('openfeature')).toBeInTheDocument();
  96. expect(screen.getByText('launchdarkly')).toBeInTheDocument();
  97. expect(
  98. screen.getByLabelText('Remove secret for launchdarkly provider')
  99. ).toBeEnabled();
  100. await userEvent.click(
  101. screen.getByLabelText('Remove secret for launchdarkly provider')
  102. );
  103. // Confirm modal
  104. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  105. expect(screen.getByText('openfeature')).toBeInTheDocument();
  106. expect(screen.queryByText('launchdarkly')).not.toBeInTheDocument();
  107. expect(indicators.addSuccessMessage).toHaveBeenCalledWith(
  108. 'Removed the provider and signing secret for the organization.'
  109. );
  110. expect(deleteMock).toHaveBeenCalledTimes(1);
  111. });
  112. it('does not allow to remove without permission', async function () {
  113. const org = OrganizationFixture({
  114. access: ['org:integrations'],
  115. });
  116. const secrets: Secret[] = [
  117. SecretFixture(),
  118. SecretFixture({
  119. id: 2,
  120. provider: 'openfeature',
  121. secret: '456def**************************',
  122. }),
  123. ];
  124. MockApiClient.addMockResponse({
  125. url: ENDPOINT,
  126. method: 'GET',
  127. body: {data: secrets},
  128. });
  129. render(<OrganizationFeatureFlagsIndex />, {organization: org});
  130. expect(await screen.findByText('launchdarkly')).toBeInTheDocument();
  131. expect(
  132. screen.getByLabelText('Remove secret for launchdarkly provider')
  133. ).toBeDisabled();
  134. });
  135. });
  136. });