github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/utils/AzureAuthProvider.test.tsx (about)

     1  /*This file is part of kuberpult.
     2  
     3  Kuberpult is free software: you can redistribute it and/or modify
     4  it under the terms of the Expat(MIT) License as published by
     5  the Free Software Foundation.
     6  
     7  Kuberpult is distributed in the hope that it will be useful,
     8  but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    10  MIT License for more details.
    11  
    12  You should have received a copy of the MIT License
    13  along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
    14  
    15  Copyright 2023 freiheit.com*/
    16  import { act, getByText, render, screen, waitFor } from '@testing-library/react';
    17  import { AcquireToken, AzureAuthProvider, AzureAuthSub, AzureAutoSignIn, Utf8ToBase64 } from './AzureAuthProvider';
    18  import { Crypto } from '@peculiar/webcrypto';
    19  import { PublicClientApplication, IPublicClientApplication, Configuration, AccountInfo } from '@azure/msal-browser';
    20  import { AuthenticatedTemplate, MsalProvider, UnauthenticatedTemplate } from '@azure/msal-react';
    21  import { AuthenticationResult } from '@azure/msal-common';
    22  import { UpdateFrontendConfig } from './store';
    23  import { BrowserHeaders } from 'browser-headers';
    24  
    25  const makeAuthenticationResult = (partial: Partial<AuthenticationResult>): AuthenticationResult => ({
    26      authority: 'authority',
    27      uniqueId: 'unqueId',
    28      tenantId: 'tenantId',
    29      scopes: [],
    30      account: null,
    31      idToken: 'idToken',
    32      idTokenClaims: {},
    33      accessToken: 'accessToken',
    34      fromCache: false,
    35      expiresOn: null,
    36      tokenType: 'tokenType',
    37      correlationId: 'correlationId',
    38      ...partial,
    39  });
    40  
    41  describe('AuthProvider', () => {
    42      let pca: IPublicClientApplication;
    43      const clientId = 'db7fc493-a2fd-4f49-aff3-0aec08f03516';
    44      const tenantId = '3912f51d-cb71-4c9f-b0b9-f55c90238dd9';
    45      const testAccount: AccountInfo = {
    46          homeAccountId: '',
    47          localAccountId: '',
    48          environment: '',
    49          tenantId,
    50          username: 'mail@example.com',
    51          name: 'test person',
    52      };
    53      const msalConfig: Configuration = {
    54          auth: {
    55              clientId,
    56          },
    57      };
    58  
    59      beforeAll(() => {
    60          Object.defineProperty(window, 'crypto', {
    61              value: new Crypto(),
    62          });
    63      });
    64  
    65      beforeEach(() => {
    66          pca = new PublicClientApplication(msalConfig);
    67          global.document.cookie = '';
    68      });
    69  
    70      afterEach(() => {
    71          // cleanup on exiting
    72          jest.clearAllMocks();
    73      });
    74  
    75      describe('Authenticated', () => {
    76          it('Show Authenticated template when logged in', async () => {
    77              const handleRedirectSpy = jest.spyOn(pca, 'handleRedirectPromise');
    78              const getAllAccountsSpy = jest.spyOn(pca, 'getAllAccounts');
    79              getAllAccountsSpy.mockImplementation(() => [testAccount]);
    80              render(
    81                  <MsalProvider instance={pca}>
    82                      <AuthenticatedTemplate>Authenticated</AuthenticatedTemplate>
    83                  </MsalProvider>
    84              );
    85              await act(async (): Promise<void> => await global.nextTick());
    86              await waitFor(() => expect(handleRedirectSpy).toHaveBeenCalledTimes(1));
    87              expect(screen.getByText('Authenticated')).toBeInTheDocument();
    88          });
    89      });
    90  
    91      describe('AzureAutoSignIn', () => {
    92          it('Redirect when not logged in', async () => {
    93              const handleRedirectSpy = jest.spyOn(pca, 'handleRedirectPromise');
    94              const getAllAccountsSpy = jest.spyOn(pca, 'getAllAccounts');
    95              const redirectSpy = jest.spyOn(pca, 'loginRedirect');
    96              getAllAccountsSpy.mockImplementation(() => []);
    97              render(
    98                  <MsalProvider instance={pca}>
    99                      <UnauthenticatedTemplate>
   100                          <AzureAutoSignIn />
   101                      </UnauthenticatedTemplate>
   102                  </MsalProvider>
   103              );
   104              await act(async () => await global.nextTick());
   105              await waitFor(() => expect(redirectSpy).toHaveBeenCalledTimes(1));
   106              await waitFor(() => expect(handleRedirectSpy).toHaveBeenCalledTimes(1));
   107          });
   108      });
   109  
   110      describe('AcquireToken', () => {
   111          it('Get id token and store it in authHeader with acquireTokenSilent method', async () => {
   112              // given
   113              jest.spyOn(pca, 'getAllAccounts').mockImplementation(() => [testAccount]);
   114              const acquireTokenSilentSpy = jest
   115                  .spyOn(pca, 'acquireTokenSilent')
   116                  .mockImplementation((r) => Promise.resolve(makeAuthenticationResult({ idToken: 'unique-token' })));
   117  
   118              // when
   119              render(
   120                  <MsalProvider instance={pca}>
   121                      <AuthenticatedTemplate>
   122                          <AcquireToken>
   123                              <span>Token Acquired</span>
   124                          </AcquireToken>
   125                      </AuthenticatedTemplate>
   126                  </MsalProvider>
   127              );
   128              await act(async () => await global.nextTick());
   129  
   130              // then
   131              expect(screen.queryByText('Token Acquired')).toBeInTheDocument();
   132              await waitFor(async () => expect(acquireTokenSilentSpy).toHaveBeenCalledTimes(1));
   133              await waitFor(() => expect(AzureAuthSub.get().authHeader.get('authorization')).toContain('unique-token'));
   134              await waitFor(() =>
   135                  expect(AzureAuthSub.get().authHeader.get('author-email')).toContain(Utf8ToBase64('mail@example.com'))
   136              );
   137              await waitFor(() =>
   138                  expect(AzureAuthSub.get().authHeader.get('author-name')).toContain(Utf8ToBase64('test person'))
   139              );
   140          });
   141  
   142          it('Get id token and store it in authHeader with acquireTokenPopup method', async () => {
   143              // given
   144              jest.spyOn(pca, 'getAllAccounts').mockImplementation(() => [testAccount]);
   145              const acquireTokenSilentSpy = jest
   146                  .spyOn(pca, 'acquireTokenSilent')
   147                  .mockImplementation((r) => Promise.reject(new Error('promise failed testing')));
   148              const acquireTokenPopup = jest
   149                  .spyOn(pca, 'acquireTokenPopup')
   150                  .mockImplementation((r) => Promise.resolve(makeAuthenticationResult({ idToken: 'unique-token-2' })));
   151  
   152              // when
   153              render(
   154                  <MsalProvider instance={pca}>
   155                      <AuthenticatedTemplate>
   156                          <AcquireToken>
   157                              <span>Token Acquired</span>
   158                          </AcquireToken>
   159                      </AuthenticatedTemplate>
   160                  </MsalProvider>
   161              );
   162              await act(async () => await global.nextTick());
   163  
   164              // then
   165              expect(screen.queryByText('Token Acquired')).toBeInTheDocument();
   166              await waitFor(async () => expect(acquireTokenSilentSpy).toHaveBeenCalledTimes(1));
   167              await waitFor(async () => expect(acquireTokenPopup).toHaveBeenCalledTimes(1));
   168              await waitFor(() => expect(AzureAuthSub.get().authHeader.get('authorization')).toContain('unique-token-2'));
   169              await waitFor(() =>
   170                  expect(AzureAuthSub.get().authHeader.get('author-email')).toContain(Utf8ToBase64('mail@example.com'))
   171              );
   172              await waitFor(() =>
   173                  expect(AzureAuthSub.get().authHeader.get('author-name')).toContain(Utf8ToBase64('test person'))
   174              );
   175          });
   176          it('Get id token both method failed', async () => {
   177              // given
   178              AzureAuthSub.set({
   179                  authHeader: new BrowserHeaders({}),
   180                  authReady: false,
   181              });
   182              // eslint-disable-next-line no-console
   183              console.error = jest.fn();
   184  
   185              jest.spyOn(pca, 'getAllAccounts').mockImplementation(() => [testAccount]);
   186              const acquireTokenSilentSpy = jest
   187                  .spyOn(pca, 'acquireTokenSilent')
   188                  .mockImplementation((r) => Promise.reject(new Error('promise failed testing 1')));
   189              const acquireTokenPopup = jest
   190                  .spyOn(pca, 'acquireTokenPopup')
   191                  .mockImplementation((r) => Promise.reject(new Error('promise failed testing 2')));
   192  
   193              // when
   194              render(
   195                  <MsalProvider instance={pca}>
   196                      <AuthenticatedTemplate>
   197                          <AcquireToken>
   198                              <span>Token Acquired</span>
   199                          </AcquireToken>
   200                      </AuthenticatedTemplate>
   201                  </MsalProvider>
   202              );
   203              await act(async () => await global.nextTick());
   204  
   205              // then
   206              await waitFor(async () => expect(acquireTokenSilentSpy).toHaveBeenCalledTimes(1));
   207              await waitFor(async () => expect(acquireTokenPopup).toHaveBeenCalledTimes(1));
   208              // eslint-disable-next-line no-console
   209              expect(console.error).toHaveBeenCalledWith(
   210                  'acquireTokenSilent failed: ',
   211                  new Error('promise failed testing 1')
   212              );
   213              // eslint-disable-next-line no-console
   214              expect(console.error).toHaveBeenCalledWith(
   215                  'acquireTokenPopup failed: ',
   216                  new Error('promise failed testing 2')
   217              );
   218              expect(screen.queryByText('Token Acquired')).not.toBeInTheDocument();
   219              expect(screen.queryByText('loading...')).toBeInTheDocument();
   220          });
   221      });
   222  
   223      describe('Azure Not Enabled Test', () => {
   224          it('Provider can display content when azure auth is off', () => {
   225              // given
   226              UpdateFrontendConfig.set({
   227                  configs: {
   228                      manifestRepoUrl: 'myrepo',
   229                      sourceRepoUrl: 'mysource',
   230                      branch: 'main',
   231                      kuberpultVersion: '1.2.3',
   232                      authConfig: {
   233                          azureAuth: {
   234                              enabled: false,
   235                              clientId: 'none',
   236                              tenantId: 'no-tenant',
   237                              cloudInstance: 'myinstance',
   238                              redirectUrl: 'example.com',
   239                          },
   240                      },
   241                  },
   242                  configReady: true,
   243              });
   244  
   245              // when
   246              const { container } = render(
   247                  <AzureAuthProvider>
   248                      <div className={'welcome-kuberpult-test'}>Welcome to kuberpult</div>
   249                  </AzureAuthProvider>
   250              );
   251  
   252              // then
   253              expect(getByText(container, /Welcome to kuberpult/i)).toBeInTheDocument();
   254          });
   255      });
   256      describe('Spinner renders', () => {
   257          it('Spinner is rendered when the config is not loaded', () => {
   258              // given
   259              UpdateFrontendConfig.set({
   260                  configReady: false,
   261              });
   262  
   263              // when
   264              const { container } = render(
   265                  <AzureAuthProvider>
   266                      <div className={'welcome-kuberpult-test'}>Welcome to kuberpult</div>
   267                  </AzureAuthProvider>
   268              );
   269  
   270              // then
   271              expect(container.getElementsByClassName('spinner')).toHaveLength(1);
   272          });
   273      });
   274  });