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 });