3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
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';
37 jest.mock('../../../../../api/settings');
38 jest.mock('../../../../../api/system');
40 let handler: AuthenticationServiceMock;
41 let system: SystemServiceMock;
42 let settingsHandler: SettingsServiceMock;
43 let computeEngineHandler: ComputeEngineServiceMock;
46 handler = new AuthenticationServiceMock();
47 system = new SystemServiceMock();
48 settingsHandler = new SettingsServiceMock();
49 computeEngineHandler = new ComputeEngineServiceMock();
52 key: 'sonar.auth.saml.signature.enabled',
56 key: 'sonar.auth.saml.enabled',
60 key: 'sonar.auth.saml.applicationId',
64 key: 'sonar.auth.saml.providerName',
67 ].forEach((setting: any) => settingsHandler.set(setting.key, setting.value));
72 settingsHandler.reset();
74 computeEngineHandler.reset();
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' }),
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',
104 scimProvisioningButton: byRole('radio', {
105 name: 'settings.authentication.saml.form.provisioning_with_scim',
107 fillForm: async (user: UserEvent) => {
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');
119 createConfiguration: async (user: UserEvent) => {
121 await act(async () => {
122 await user.click((await saml.createConfigButton.findAll())[0]);
124 await saml.fillForm(user);
125 await act(async () => {
126 await user.click(saml.saveConfigButton.get());
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',
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) =>
154 name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
156 enableFirstMessage: byText('settings.authentication.github.enable_first'),
157 jitProvisioningButton: byRole('radio', {
158 name: 'settings.authentication.form.provisioning_at_login',
160 githubProvisioningButton: byRole('radio', {
161 name: 'settings.authentication.github.form.provisioning_with_github',
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/,
170 configurationValiditySuccess: byRole('status', {
171 name: /github.configuration.validation.valid/,
173 configurationValidityError: byRole('alert', {
174 name: /github.configuration.validation.invalid/,
176 checkConfigButton: byRole('button', {
177 name: 'settings.authentication.github.configuration.validation.test',
179 viewConfigValidityDetailsButton: byRole('button', {
180 name: 'settings.authentication.github.configuration.validation.details',
182 configDetailsDialog: byRole('dialog', {
183 name: 'settings.authentication.github.configuration.validation.details.title',
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');
197 createConfiguration: async (user: UserEvent) => {
198 const { github } = ui;
199 await act(async () => {
200 await user.click((await github.createConfigButton.findAll())[1]);
202 await github.fillForm(user);
203 await act(async () => {
204 await user.click(github.saveConfigButton.get());
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()));
213 enableProvisioning: async (user: UserEvent) => {
214 const { github } = ui;
215 await act(async () => user.click(await github.tab.find()));
217 await github.createConfiguration(user);
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()));
227 it('should render tabs and allow navigation', async () => {
228 const user = userEvent.setup();
229 renderAuthentication();
231 expect(screen.getAllByRole('tab')).toHaveLength(4);
233 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
235 await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
237 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
238 expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
244 it('should not display the login message feature info box', () => {
245 renderAuthentication();
247 expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
250 it('should display the login message feature info box', () => {
251 renderAuthentication([Feature.LoginMessage]);
253 expect(ui.customMessageInformation.get()).toBeInTheDocument();
256 describe('SAML tab', () => {
259 it('should render an empty SAML configuration', async () => {
260 renderAuthentication();
261 expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
264 it('should be able to create a configuration', async () => {
265 const user = userEvent.setup();
266 renderAuthentication();
268 await user.click((await saml.createConfigButton.findAll())[0]);
270 expect(saml.saveConfigButton.get()).toBeDisabled();
271 await saml.fillForm(user);
272 expect(saml.saveConfigButton.get()).toBeEnabled();
274 await act(async () => {
275 await user.click(saml.saveConfigButton.get());
278 expect(await saml.editConfigButton.find()).toBeInTheDocument();
281 it('should be able to enable/disable configuration', async () => {
283 const user = userEvent.setup();
284 renderAuthentication();
286 await saml.createConfiguration(user);
287 await user.click(await saml.enableConfigButton.find());
289 expect(await saml.disableConfigButton.find()).toBeInTheDocument();
290 await user.click(saml.disableConfigButton.get());
291 expect(saml.disableConfigButton.query()).not.toBeInTheDocument();
293 expect(await saml.enableConfigButton.find()).toBeInTheDocument();
296 it('should be able to choose provisioning', async () => {
298 const user = userEvent.setup();
300 renderAuthentication([Feature.Scim]);
302 await saml.createConfiguration(user);
304 expect(await saml.enableFirstMessage.find()).toBeInTheDocument();
305 await user.click(await saml.enableConfigButton.find());
307 expect(await saml.jitProvisioningButton.find()).toBeChecked();
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();
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());
319 expect(await saml.scimProvisioningButton.find()).toBeChecked();
320 expect(await saml.saveScim.find()).toBeDisabled();
323 it('should not allow editions below Enterprise to select SCIM provisioning', async () => {
325 const user = userEvent.setup();
327 renderAuthentication();
329 await saml.createConfiguration(user);
330 await user.click(await saml.enableConfigButton.find());
332 expect(await saml.jitProvisioningButton.find()).toBeChecked();
333 expect(saml.scimProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
337 describe('Github tab', () => {
338 const { github } = ui;
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();
347 it('should be able to create a configuration', async () => {
348 const user = userEvent.setup();
349 renderAuthentication();
351 await user.click(await github.tab.find());
352 await user.click((await github.createConfigButton.findAll())[1]);
354 expect(github.saveConfigButton.get()).toBeDisabled();
356 await github.fillForm(user);
357 expect(github.saveConfigButton.get()).toBeEnabled();
359 await act(async () => {
360 await user.click(github.saveConfigButton.get());
363 expect(await github.editConfigButton.find()).toBeInTheDocument();
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());
372 await github.createConfiguration(user);
374 await user.click(github.editConfigButton.get());
375 await user.click(github.deleteOrg('organization1').get());
377 await user.click(github.saveConfigButton.get());
379 await user.click(await github.editConfigButton.find());
381 expect(github.organizations.get()).toHaveValue('');
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());
390 await github.createConfiguration(user);
392 await user.click(await github.enableConfigButton.find());
394 expect(await github.disableConfigButton.find()).toBeInTheDocument();
395 await user.click(github.disableConfigButton.get());
396 expect(github.disableConfigButton.query()).not.toBeInTheDocument();
398 expect(await github.enableConfigButton.find()).toBeInTheDocument();
401 it('should not allow edtion below Enterprise to select Github provisioning', async () => {
402 const { github } = ui;
403 const user = userEvent.setup();
405 renderAuthentication();
407 await github.createConfiguration(user);
408 await user.click(await github.enableConfigButton.find());
410 expect(await github.jitProvisioningButton.find()).toBeChecked();
411 expect(github.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
414 it('should be able to choose provisioning', async () => {
415 const { github } = ui;
416 const user = userEvent.setup();
418 renderAuthentication([Feature.GithubProvisioning]);
419 await user.click(await github.tab.find());
421 await github.createConfiguration(user);
423 expect(await github.enableFirstMessage.find()).toBeInTheDocument();
424 await user.click(await github.enableConfigButton.find());
426 expect(await github.jitProvisioningButton.find()).toBeChecked();
428 expect(github.saveGithubProvisioning.get()).toBeDisabled();
429 await user.click(github.allowUserToSignUp.get());
430 await user.click(github.syncGroupsAsTeams.get());
432 expect(github.saveGithubProvisioning.get()).toBeEnabled();
433 await user.click(github.saveGithubProvisioning.get());
434 expect(await github.saveGithubProvisioning.find()).toBeDisabled();
436 await user.click(github.githubProvisioningButton.get());
438 expect(github.saveGithubProvisioning.get()).toBeEnabled();
439 await user.click(github.saveGithubProvisioning.get());
440 await user.click(github.confirmProvisioningButton.get());
442 expect(await github.githubProvisioningButton.find()).toBeChecked();
443 expect(github.disableConfigButton.get()).toBeDisabled();
444 expect(github.saveGithubProvisioning.get()).toBeDisabled();
447 describe('Github Provisioning', () => {
452 now: new Date('2022-02-04T12:00:59Z'),
454 user = userEvent.setup();
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',
462 renderAuthentication([Feature.GithubProvisioning]);
463 await github.enableProvisioning(user);
464 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
465 expect(github.githubProvisioningButton.get()).toHaveTextContent('Test summary');
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',
473 handler.addProvisioningTask({
474 status: TaskStatuses.Success,
475 executedAt: '2022-02-03T11:45:35+0200',
477 renderAuthentication([Feature.GithubProvisioning]);
478 await github.enableProvisioning(user);
479 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
480 expect(github.githubProvisioningPending.get()).toBeInTheDocument();
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",
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();
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',
501 handler.addProvisioningTask({
502 status: TaskStatuses.Failed,
503 executedAt: '2022-02-03T11:45:35+0200',
504 errorMessage: "T'es mauvais Jacques",
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();
514 it('should display that config is valid for both provisioning with 1 org', async () => {
515 renderAuthentication([Feature.GithubProvisioning]);
516 await github.enableConfiguration(user);
518 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
521 it('should display that config is valid for both provisioning with multiple orgs', async () => {
522 handler.setConfigurationValidity({
524 { organization: 'org1', autoProvisioning: { status: GitHubProvisioningStatus.Success } },
525 { organization: 'org2', autoProvisioning: { status: GitHubProvisioningStatus.Success } },
528 renderAuthentication([Feature.GithubProvisioning]);
529 await github.enableConfiguration(user);
531 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
532 expect(github.configurationValiditySuccess.get()).toHaveTextContent('2');
534 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
535 expect(github.getConfigDetailsTitle()).toHaveTextContent(
536 'settings.authentication.github.configuration.validation.details.valid_label'
538 expect(github.getOrgs()[0]).toHaveTextContent(
539 'settings.authentication.github.configuration.validation.details.valid_labelorg1'
541 expect(github.getOrgs()[1]).toHaveTextContent(
542 'settings.authentication.github.configuration.validation.details.valid_labelorg2'
546 it('should display that config is invalid', async () => {
547 const errorMessage = 'Test error';
548 handler.setConfigurationValidity({
551 status: GitHubProvisioningStatus.Error,
555 status: GitHubProvisioningStatus.Error,
560 renderAuthentication([Feature.GithubProvisioning]);
561 await github.enableConfiguration(user);
563 expect(github.configurationValidityError.get()).toBeInTheDocument();
564 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
566 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
567 expect(github.getConfigDetailsTitle()).toHaveTextContent(
568 'settings.authentication.github.configuration.validation.details.invalid_label'
570 expect(github.configDetailsDialog.get()).toHaveTextContent(errorMessage);
573 it('should display that config is valid for jit, but not for auto', async () => {
574 const errorMessage = 'Test error';
575 handler.setConfigurationValidity({
578 status: GitHubProvisioningStatus.Success,
581 status: GitHubProvisioningStatus.Error,
586 renderAuthentication([Feature.GithubProvisioning]);
587 await github.enableConfiguration(user);
589 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
590 expect(github.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
592 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
593 expect(github.getConfigDetailsTitle()).toHaveTextContent(
594 'settings.authentication.github.configuration.validation.details.valid_label'
597 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' }))
600 await act(() => user.click(github.githubProvisioningButton.get()));
602 expect(github.configurationValidityError.get()).toBeInTheDocument();
603 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
605 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
606 expect(github.getConfigDetailsTitle()).toHaveTextContent(
607 'settings.authentication.github.configuration.validation.details.invalid_label'
611 it('should display that config is invalid because of orgs', async () => {
612 const errorMessage = 'Test error';
613 handler.setConfigurationValidity({
615 { organization: 'org1', autoProvisioning: { status: GitHubProvisioningStatus.Success } },
617 organization: 'org2',
618 autoProvisioning: { status: GitHubProvisioningStatus.Error, errorMessage },
622 renderAuthentication([Feature.GithubProvisioning]);
623 await github.enableConfiguration(user);
625 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
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'
634 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' }))
637 await act(() => user.click(github.githubProvisioningButton.get()));
639 expect(github.configurationValidityError.get()).toBeInTheDocument();
640 expect(github.configurationValidityError.get()).toHaveTextContent(
641 `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`
643 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
644 expect(github.getOrgs()[1]).toHaveTextContent(
645 `settings.authentication.github.configuration.validation.details.invalid_labelorg2 - ${errorMessage}`
649 it('should update provisioning validity after clicking Test Configuration', async () => {
650 const errorMessage = 'Test error';
651 handler.setConfigurationValidity({
654 status: GitHubProvisioningStatus.Error,
658 status: GitHubProvisioningStatus.Error,
663 renderAuthentication([Feature.GithubProvisioning]);
664 await github.enableConfiguration(user);
665 handler.setConfigurationValidity({
668 status: GitHubProvisioningStatus.Success,
671 status: GitHubProvisioningStatus.Success,
676 expect(await github.configurationValidityError.find()).toBeInTheDocument();
678 await act(() => user.click(github.checkConfigButton.get()));
680 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
681 expect(github.configurationValidityError.query()).not.toBeInTheDocument();
686 function renderAuthentication(features: Feature[] = []) {
688 <AvailableFeaturesContext.Provider value={features}>
689 <Authentication definitions={definitions} />
690 </AvailableFeaturesContext.Provider>