]> source.dussan.org Git - sonarqube.git/blob
fd56ab5477fca8881d61dcdd7b71e61ff8c754ab
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 import { act, screen, within } from '@testing-library/react';
21 import userEvent from '@testing-library/user-event';
22 import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
23 import React from 'react';
24 import { byRole, byText } from 'testing-library-selector';
25 import AuthenticationServiceMock from '../../../../../api/mocks/AuthenticationServiceMock';
26 import ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
27 import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
28 import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
29 import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
30 import { definitions } from '../../../../../helpers/mocks/definitions-list';
31 import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
32 import { Feature } from '../../../../../types/features';
33 import { GitHubProvisioningStatus } from '../../../../../types/provisioning';
34 import { TaskStatuses } from '../../../../../types/tasks';
35 import Authentication from '../Authentication';
36
37 jest.mock('../../../../../api/settings');
38 jest.mock('../../../../../api/system');
39
40 let handler: AuthenticationServiceMock;
41 let system: SystemServiceMock;
42 let settingsHandler: SettingsServiceMock;
43 let computeEngineHandler: ComputeEngineServiceMock;
44
45 beforeEach(() => {
46   handler = new AuthenticationServiceMock();
47   system = new SystemServiceMock();
48   settingsHandler = new SettingsServiceMock();
49   computeEngineHandler = new ComputeEngineServiceMock();
50   [
51     {
52       key: 'sonar.auth.saml.signature.enabled',
53       value: 'false',
54     },
55     {
56       key: 'sonar.auth.saml.enabled',
57       value: 'false',
58     },
59     {
60       key: 'sonar.auth.saml.applicationId',
61       value: 'sonarqube',
62     },
63     {
64       key: 'sonar.auth.saml.providerName',
65       value: 'SAML',
66     },
67   ].forEach((setting: any) => settingsHandler.set(setting.key, setting.value));
68 });
69
70 afterEach(() => {
71   handler.reset();
72   settingsHandler.reset();
73   system.reset();
74   computeEngineHandler.reset();
75 });
76
77 const ui = {
78   saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
79   customMessageInformation: byText('settings.authentication.custom_message_information'),
80   enabledToggle: byRole('switch'),
81   testButton: byText('settings.authentication.saml.form.test'),
82   textbox1: byRole('textbox', { name: 'test1' }),
83   textbox2: byRole('textbox', { name: 'test2' }),
84   saml: {
85     noSamlConfiguration: byText('settings.authentication.saml.form.not_configured'),
86     createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
87     providerName: byRole('textbox', { name: 'Provider Name' }),
88     providerId: byRole('textbox', { name: 'Provider ID' }),
89     providerCertificate: byRole('textbox', { name: 'Identity provider certificate' }),
90     loginUrl: byRole('textbox', { name: 'SAML login url' }),
91     userLoginAttribute: byRole('textbox', { name: 'SAML user login attribute' }),
92     userNameAttribute: byRole('textbox', { name: 'SAML user name attribute' }),
93     saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
94     confirmProvisioningButton: byRole('button', { name: 'yes' }),
95     saveScim: byRole('button', { name: 'save' }),
96     groupAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.group.name.name' }),
97     enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
98     disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
99     editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
100     enableFirstMessage: byText('settings.authentication.saml.enable_first'),
101     jitProvisioningButton: byRole('radio', {
102       name: 'settings.authentication.saml.form.provisioning_at_login',
103     }),
104     scimProvisioningButton: byRole('radio', {
105       name: 'settings.authentication.saml.form.provisioning_with_scim',
106     }),
107     fillForm: async (user: UserEvent) => {
108       const { saml } = ui;
109       await act(async () => {
110         await user.clear(saml.providerName.get());
111         await user.type(saml.providerName.get(), 'Awsome SAML config');
112         await user.type(saml.providerId.get(), 'okta-1234');
113         await user.type(saml.loginUrl.get(), 'http://test.org');
114         await user.type(saml.providerCertificate.get(), '-secret-');
115         await user.type(saml.userLoginAttribute.get(), 'login');
116         await user.type(saml.userNameAttribute.get(), 'name');
117       });
118     },
119     createConfiguration: async (user: UserEvent) => {
120       const { saml } = ui;
121       await act(async () => {
122         await user.click((await saml.createConfigButton.findAll())[0]);
123       });
124       await saml.fillForm(user);
125       await act(async () => {
126         await user.click(saml.saveConfigButton.get());
127       });
128     },
129   },
130   github: {
131     tab: byRole('tab', { name: 'github GitHub' }),
132     noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
133     createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
134     clientId: byRole('textbox', { name: 'Client ID' }),
135     clientSecret: byRole('textbox', { name: 'Client Secret' }),
136     githubAppId: byRole('textbox', { name: 'GitHub App ID' }), // not working
137     privateKey: byRole('textarea', { name: 'Private Key' }), // not working
138     githubApiUrl: byRole('textbox', { name: 'The API url for a GitHub instance.' }),
139     githubWebUrl: byRole('textbox', { name: 'The WEB url for a GitHub instance.' }),
140     allowUserToSignUp: byRole('switch', {
141       name: 'sonar.auth.github.allowUsersToSignUp',
142     }),
143     syncGroupsAsTeams: byRole('switch', { name: 'sonar.auth.github.groupsSync' }),
144     organizations: byRole('textbox', { name: 'Organizations' }),
145     saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
146     confirmProvisioningButton: byRole('button', { name: 'yes' }),
147     saveGithubProvisioning: byRole('button', { name: 'save' }),
148     groupAttribute: byRole('textbox', { name: 'property.sonar.auth.github.group.name.name' }),
149     enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
150     disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
151     editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
152     deleteOrg: (org: string) =>
153       byRole('button', {
154         name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
155       }),
156     enableFirstMessage: byText('settings.authentication.github.enable_first'),
157     jitProvisioningButton: byRole('radio', {
158       name: 'settings.authentication.form.provisioning_at_login',
159     }),
160     githubProvisioningButton: byRole('radio', {
161       name: 'settings.authentication.github.form.provisioning_with_github',
162     }),
163     githubProvisioningPending: byText(/synchronization_pending/),
164     githubProvisioningInProgress: byText(/synchronization_in_progress/),
165     githubProvisioningSuccess: byText(/synchronization_successful/),
166     githubProvisioningAlert: byText(/synchronization_failed/),
167     configurationValidityLoading: byRole('status', {
168       name: /github.configuration.validation.loading/,
169     }),
170     configurationValiditySuccess: byRole('status', {
171       name: /github.configuration.validation.valid/,
172     }),
173     configurationValidityError: byRole('alert', {
174       name: /github.configuration.validation.invalid/,
175     }),
176     checkConfigButton: byRole('button', {
177       name: 'settings.authentication.github.configuration.validation.test',
178     }),
179     viewConfigValidityDetailsButton: byRole('button', {
180       name: 'settings.authentication.github.configuration.validation.details',
181     }),
182     configDetailsDialog: byRole('dialog', {
183       name: 'settings.authentication.github.configuration.validation.details.title',
184     }),
185     getConfigDetailsTitle: () => within(ui.github.configDetailsDialog.get()).getByRole('heading'),
186     getOrgs: () => within(ui.github.configDetailsDialog.get()).getAllByRole('listitem'),
187     fillForm: async (user: UserEvent) => {
188       const { github } = ui;
189       await act(async () => {
190         await user.type(await github.clientId.find(), 'Awsome GITHUB config');
191         await user.type(github.clientSecret.get(), 'Client shut');
192         await user.type(github.githubApiUrl.get(), 'API Url');
193         await user.type(github.githubWebUrl.get(), 'WEb Url');
194         await user.type(github.organizations.get(), 'organization1');
195       });
196     },
197     createConfiguration: async (user: UserEvent) => {
198       const { github } = ui;
199       await act(async () => {
200         await user.click((await github.createConfigButton.findAll())[1]);
201       });
202       await github.fillForm(user);
203       await act(async () => {
204         await user.click(github.saveConfigButton.get());
205       });
206     },
207     enableConfiguration: async (user: UserEvent) => {
208       const { github } = ui;
209       await act(async () => user.click(await github.tab.find()));
210       await github.createConfiguration(user);
211       await act(async () => user.click(await github.enableConfigButton.find()));
212     },
213     enableProvisioning: async (user: UserEvent) => {
214       const { github } = ui;
215       await act(async () => user.click(await github.tab.find()));
216
217       await github.createConfiguration(user);
218
219       await act(async () => user.click(await github.enableConfigButton.find()));
220       await user.click(await github.githubProvisioningButton.find());
221       await user.click(github.saveGithubProvisioning.get());
222       await act(() => user.click(github.confirmProvisioningButton.get()));
223     },
224   },
225 };
226
227 it('should render tabs and allow navigation', async () => {
228   const user = userEvent.setup();
229   renderAuthentication();
230
231   expect(screen.getAllByRole('tab')).toHaveLength(4);
232
233   expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
234
235   await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
236
237   expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
238   expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
239     'aria-selected',
240     'true'
241   );
242 });
243
244 it('should not display the login message feature info box', () => {
245   renderAuthentication();
246
247   expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
248 });
249
250 it('should display the login message feature info box', () => {
251   renderAuthentication([Feature.LoginMessage]);
252
253   expect(ui.customMessageInformation.get()).toBeInTheDocument();
254 });
255
256 describe('SAML tab', () => {
257   const { saml } = ui;
258
259   it('should render an empty SAML configuration', async () => {
260     renderAuthentication();
261     expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
262   });
263
264   it('should be able to create a configuration', async () => {
265     const user = userEvent.setup();
266     renderAuthentication();
267
268     await user.click((await saml.createConfigButton.findAll())[0]);
269
270     expect(saml.saveConfigButton.get()).toBeDisabled();
271     await saml.fillForm(user);
272     expect(saml.saveConfigButton.get()).toBeEnabled();
273
274     await act(async () => {
275       await user.click(saml.saveConfigButton.get());
276     });
277
278     expect(await saml.editConfigButton.find()).toBeInTheDocument();
279   });
280
281   it('should be able to enable/disable configuration', async () => {
282     const { saml } = ui;
283     const user = userEvent.setup();
284     renderAuthentication();
285
286     await saml.createConfiguration(user);
287     await user.click(await saml.enableConfigButton.find());
288
289     expect(await saml.disableConfigButton.find()).toBeInTheDocument();
290     await user.click(saml.disableConfigButton.get());
291     expect(saml.disableConfigButton.query()).not.toBeInTheDocument();
292
293     expect(await saml.enableConfigButton.find()).toBeInTheDocument();
294   });
295
296   it('should be able to choose provisioning', async () => {
297     const { saml } = ui;
298     const user = userEvent.setup();
299
300     renderAuthentication([Feature.Scim]);
301
302     await saml.createConfiguration(user);
303
304     expect(await saml.enableFirstMessage.find()).toBeInTheDocument();
305     await user.click(await saml.enableConfigButton.find());
306
307     expect(await saml.jitProvisioningButton.find()).toBeChecked();
308
309     await user.type(saml.groupAttribute.get(), 'group');
310     expect(saml.saveScim.get()).toBeEnabled();
311     await user.click(saml.saveScim.get());
312     expect(await saml.saveScim.find()).toBeDisabled();
313
314     await user.click(saml.scimProvisioningButton.get());
315     expect(saml.saveScim.get()).toBeEnabled();
316     await user.click(saml.saveScim.get());
317     await user.click(saml.confirmProvisioningButton.get());
318
319     expect(await saml.scimProvisioningButton.find()).toBeChecked();
320     expect(await saml.saveScim.find()).toBeDisabled();
321   });
322
323   it('should not allow editions below Enterprise to select SCIM provisioning', async () => {
324     const { saml } = ui;
325     const user = userEvent.setup();
326
327     renderAuthentication();
328
329     await saml.createConfiguration(user);
330     await user.click(await saml.enableConfigButton.find());
331
332     expect(await saml.jitProvisioningButton.find()).toBeChecked();
333     expect(saml.scimProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
334   });
335 });
336
337 describe('Github tab', () => {
338   const { github } = ui;
339
340   it('should render an empty Github configuration', async () => {
341     renderAuthentication();
342     const user = userEvent.setup();
343     await user.click(await github.tab.find());
344     expect(await github.noGithubConfiguration.find()).toBeInTheDocument();
345   });
346
347   it('should be able to create a configuration', async () => {
348     const user = userEvent.setup();
349     renderAuthentication();
350
351     await user.click(await github.tab.find());
352     await user.click((await github.createConfigButton.findAll())[1]);
353
354     expect(github.saveConfigButton.get()).toBeDisabled();
355
356     await github.fillForm(user);
357     expect(github.saveConfigButton.get()).toBeEnabled();
358
359     await act(async () => {
360       await user.click(github.saveConfigButton.get());
361     });
362
363     expect(await github.editConfigButton.find()).toBeInTheDocument();
364   });
365
366   it('should be able to edit configuration', async () => {
367     const { github } = ui;
368     const user = userEvent.setup();
369     renderAuthentication();
370     await user.click(await github.tab.find());
371
372     await github.createConfiguration(user);
373
374     await user.click(github.editConfigButton.get());
375     await user.click(github.deleteOrg('organization1').get());
376
377     await user.click(github.saveConfigButton.get());
378
379     await user.click(await github.editConfigButton.find());
380
381     expect(github.organizations.get()).toHaveValue('');
382   });
383
384   it('should be able to enable/disable configuration', async () => {
385     const { github } = ui;
386     const user = userEvent.setup();
387     renderAuthentication();
388     await user.click(await github.tab.find());
389
390     await github.createConfiguration(user);
391
392     await user.click(await github.enableConfigButton.find());
393
394     expect(await github.disableConfigButton.find()).toBeInTheDocument();
395     await user.click(github.disableConfigButton.get());
396     expect(github.disableConfigButton.query()).not.toBeInTheDocument();
397
398     expect(await github.enableConfigButton.find()).toBeInTheDocument();
399   });
400
401   it('should not allow edtion below Enterprise to select Github provisioning', async () => {
402     const { github } = ui;
403     const user = userEvent.setup();
404
405     renderAuthentication();
406
407     await github.createConfiguration(user);
408     await user.click(await github.enableConfigButton.find());
409
410     expect(await github.jitProvisioningButton.find()).toBeChecked();
411     expect(github.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
412   });
413
414   it('should be able to choose provisioning', async () => {
415     const { github } = ui;
416     const user = userEvent.setup();
417
418     renderAuthentication([Feature.GithubProvisioning]);
419     await user.click(await github.tab.find());
420
421     await github.createConfiguration(user);
422
423     expect(await github.enableFirstMessage.find()).toBeInTheDocument();
424     await user.click(await github.enableConfigButton.find());
425
426     expect(await github.jitProvisioningButton.find()).toBeChecked();
427
428     expect(github.saveGithubProvisioning.get()).toBeDisabled();
429     await user.click(github.allowUserToSignUp.get());
430     await user.click(github.syncGroupsAsTeams.get());
431
432     expect(github.saveGithubProvisioning.get()).toBeEnabled();
433     await user.click(github.saveGithubProvisioning.get());
434     expect(await github.saveGithubProvisioning.find()).toBeDisabled();
435
436     await user.click(github.githubProvisioningButton.get());
437
438     expect(github.saveGithubProvisioning.get()).toBeEnabled();
439     await user.click(github.saveGithubProvisioning.get());
440     await user.click(github.confirmProvisioningButton.get());
441
442     expect(await github.githubProvisioningButton.find()).toBeChecked();
443     expect(github.disableConfigButton.get()).toBeDisabled();
444     expect(github.saveGithubProvisioning.get()).toBeDisabled();
445   });
446
447   describe('Github Provisioning', () => {
448     let user: UserEvent;
449     beforeEach(() => {
450       jest.useFakeTimers({
451         advanceTimers: true,
452         now: new Date('2022-02-04T12:00:59Z'),
453       });
454       user = userEvent.setup();
455     });
456     it('should display a success status when the synchronisation is a success', async () => {
457       handler.addProvisioningTask({
458         status: TaskStatuses.Success,
459         executedAt: '2022-02-03T11:45:35+0200',
460       });
461
462       renderAuthentication([Feature.GithubProvisioning]);
463       await github.enableProvisioning(user);
464       expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
465       expect(github.githubProvisioningButton.get()).toHaveTextContent('Test summary');
466     });
467
468     it('should display a success status even when another task is pending', async () => {
469       handler.addProvisioningTask({
470         status: TaskStatuses.Pending,
471         executedAt: '2022-02-03T11:55:35+0200',
472       });
473       handler.addProvisioningTask({
474         status: TaskStatuses.Success,
475         executedAt: '2022-02-03T11:45:35+0200',
476       });
477       renderAuthentication([Feature.GithubProvisioning]);
478       await github.enableProvisioning(user);
479       expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
480       expect(github.githubProvisioningPending.get()).toBeInTheDocument();
481     });
482
483     it('should display an error alert when the synchronisation failed', async () => {
484       handler.addProvisioningTask({
485         status: TaskStatuses.Failed,
486         executedAt: '2022-02-03T11:45:35+0200',
487         errorMessage: "T'es mauvais Jacques",
488       });
489       renderAuthentication([Feature.GithubProvisioning]);
490       await github.enableProvisioning(user);
491       expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
492       expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
493       expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
494     });
495
496     it('should display an error alert even when another task is in progress', async () => {
497       handler.addProvisioningTask({
498         status: TaskStatuses.InProgress,
499         executedAt: '2022-02-03T11:55:35+0200',
500       });
501       handler.addProvisioningTask({
502         status: TaskStatuses.Failed,
503         executedAt: '2022-02-03T11:45:35+0200',
504         errorMessage: "T'es mauvais Jacques",
505       });
506       renderAuthentication([Feature.GithubProvisioning]);
507       await github.enableProvisioning(user);
508       expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
509       expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
510       expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
511       expect(github.githubProvisioningInProgress.get()).toBeInTheDocument();
512     });
513
514     it('should display that config is valid for both provisioning with 1 org', async () => {
515       renderAuthentication([Feature.GithubProvisioning]);
516       await github.enableConfiguration(user);
517
518       expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
519     });
520
521     it('should display that config is valid for both provisioning with multiple orgs', async () => {
522       handler.setConfigurationValidity({
523         installations: [
524           { organization: 'org1', autoProvisioning: { status: GitHubProvisioningStatus.Success } },
525           { organization: 'org2', autoProvisioning: { status: GitHubProvisioningStatus.Success } },
526         ],
527       });
528       renderAuthentication([Feature.GithubProvisioning]);
529       await github.enableConfiguration(user);
530
531       expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
532       expect(github.configurationValiditySuccess.get()).toHaveTextContent('2');
533
534       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
535       expect(github.getConfigDetailsTitle()).toHaveTextContent(
536         'settings.authentication.github.configuration.validation.details.valid_label'
537       );
538       expect(github.getOrgs()[0]).toHaveTextContent(
539         'settings.authentication.github.configuration.validation.details.valid_labelorg1'
540       );
541       expect(github.getOrgs()[1]).toHaveTextContent(
542         'settings.authentication.github.configuration.validation.details.valid_labelorg2'
543       );
544     });
545
546     it('should display that config is invalid', async () => {
547       const errorMessage = 'Test error';
548       handler.setConfigurationValidity({
549         application: {
550           jit: {
551             status: GitHubProvisioningStatus.Error,
552             errorMessage,
553           },
554           autoProvisioning: {
555             status: GitHubProvisioningStatus.Error,
556             errorMessage,
557           },
558         },
559       });
560       renderAuthentication([Feature.GithubProvisioning]);
561       await github.enableConfiguration(user);
562
563       expect(github.configurationValidityError.get()).toBeInTheDocument();
564       expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
565
566       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
567       expect(github.getConfigDetailsTitle()).toHaveTextContent(
568         'settings.authentication.github.configuration.validation.details.invalid_label'
569       );
570       expect(github.configDetailsDialog.get()).toHaveTextContent(errorMessage);
571     });
572
573     it('should display that config is valid for jit, but not for auto', async () => {
574       const errorMessage = 'Test error';
575       handler.setConfigurationValidity({
576         application: {
577           jit: {
578             status: GitHubProvisioningStatus.Success,
579           },
580           autoProvisioning: {
581             status: GitHubProvisioningStatus.Error,
582             errorMessage,
583           },
584         },
585       });
586       renderAuthentication([Feature.GithubProvisioning]);
587       await github.enableConfiguration(user);
588
589       expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
590       expect(github.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
591
592       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
593       expect(github.getConfigDetailsTitle()).toHaveTextContent(
594         'settings.authentication.github.configuration.validation.details.valid_label'
595       );
596       await act(() =>
597         user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' }))
598       );
599
600       await act(() => user.click(github.githubProvisioningButton.get()));
601
602       expect(github.configurationValidityError.get()).toBeInTheDocument();
603       expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
604
605       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
606       expect(github.getConfigDetailsTitle()).toHaveTextContent(
607         'settings.authentication.github.configuration.validation.details.invalid_label'
608       );
609     });
610
611     it('should display that config is invalid because of orgs', async () => {
612       const errorMessage = 'Test error';
613       handler.setConfigurationValidity({
614         installations: [
615           { organization: 'org1', autoProvisioning: { status: GitHubProvisioningStatus.Success } },
616           {
617             organization: 'org2',
618             autoProvisioning: { status: GitHubProvisioningStatus.Error, errorMessage },
619           },
620         ],
621       });
622       renderAuthentication([Feature.GithubProvisioning]);
623       await github.enableConfiguration(user);
624
625       expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
626
627       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
628       github.getOrgs().forEach((org) => {
629         expect(org).toHaveTextContent(
630           'settings.authentication.github.configuration.validation.details.valid_label'
631         );
632       });
633       await act(() =>
634         user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' }))
635       );
636
637       await act(() => user.click(github.githubProvisioningButton.get()));
638
639       expect(github.configurationValidityError.get()).toBeInTheDocument();
640       expect(github.configurationValidityError.get()).toHaveTextContent(
641         `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`
642       );
643       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
644       expect(github.getOrgs()[1]).toHaveTextContent(
645         `settings.authentication.github.configuration.validation.details.invalid_labelorg2 - ${errorMessage}`
646       );
647     });
648
649     it('should update provisioning validity after clicking Test Configuration', async () => {
650       const errorMessage = 'Test error';
651       handler.setConfigurationValidity({
652         application: {
653           jit: {
654             status: GitHubProvisioningStatus.Error,
655             errorMessage,
656           },
657           autoProvisioning: {
658             status: GitHubProvisioningStatus.Error,
659             errorMessage,
660           },
661         },
662       });
663       renderAuthentication([Feature.GithubProvisioning]);
664       await github.enableConfiguration(user);
665       handler.setConfigurationValidity({
666         application: {
667           jit: {
668             status: GitHubProvisioningStatus.Success,
669           },
670           autoProvisioning: {
671             status: GitHubProvisioningStatus.Success,
672           },
673         },
674       });
675
676       expect(await github.configurationValidityError.find()).toBeInTheDocument();
677
678       await act(() => user.click(github.checkConfigButton.get()));
679
680       expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
681       expect(github.configurationValidityError.query()).not.toBeInTheDocument();
682     });
683   });
684 });
685
686 function renderAuthentication(features: Feature[] = []) {
687   renderComponent(
688     <AvailableFeaturesContext.Provider value={features}>
689       <Authentication definitions={definitions} />
690     </AvailableFeaturesContext.Provider>
691   );
692 }