import 'fast-text-encoding' import { MessageChannel } from 'worker_threads' import * as esCookie from 'es-cookie' import unfetch from 'unfetch' import { verify } from '../../src/jwt' import * as utils from '../../src/utils' import * as scope from '../../src/scope' import { expectToHaveBeenCalledWithAuthcClientParam } from '../helpers' import { TEST_AUTHC_CLIENT_QUERY_STRING, TEST_CLIENT_ID, TEST_CODE_CHALLENGE, TEST_DOMAIN } from '../constants' // @ts-ignore import { InMemoryAsyncCacheNoKeys } from '../cache/shared' import { loginWithRedirectFn, setupFn } from './helpers' jest.mock('unfetch') jest.mock('es-cookie') jest.mock('../../src/jwt') jest.mock('../../src/worker/token.worker') const mockWindow = global const mockFetch = (mockWindow.fetch = unfetch) const mockVerify = verify const loginWithRedirect = loginWithRedirectFn(mockWindow, mockFetch) jest .spyOn(utils, 'bufferToBase64UrlEncoded') .mockReturnValue(TEST_CODE_CHALLENGE) jest.spyOn(utils, 'runPopup') const setup = setupFn(mockVerify) describe('AuthcClient', () => { const oldWindowLocation = window.location beforeEach(() => { // https://www.benmvp.com/blog/mocking-window-location-methods-jest-jsdom/ delete window.location window.location = Object.defineProperties( {}, { ...Object.getOwnPropertyDescriptors(oldWindowLocation), assign: { configurable: true, value: jest.fn(), }, }, ) as Location // -- mockWindow.open = jest.fn() mockWindow.addEventListener = jest.fn() mockWindow.crypto = { subtle: { digest: () => 'foo', }, getRandomValues() { return '123' }, } mockWindow.MessageChannel = MessageChannel mockWindow.Worker = {} jest.spyOn(scope, 'getUniqueScopes') sessionStorage.clear() }) afterEach(() => { mockFetch.mockReset() jest.clearAllMocks() window.location = oldWindowLocation }) describe('logout()', () => { it('removes authenticated cookie from storage', async () => { const authc = setup() authc.logout() expect(esCookie.remove).toHaveBeenCalledWith( `authc.${TEST_CLIENT_ID}.is.authenticated`, ) }) it('removes the organization hint cookie from storage', async () => { const authc = setup() authc.logout() expect(esCookie.remove).toHaveBeenCalledWith( `authc.${TEST_CLIENT_ID}.organization_hint`, ) }) it('calls `window.location.assign` with the correct url', async () => { const authc = setup() authc.logout() expect(window.location.assign).toHaveBeenCalledWith( `https://${TEST_DOMAIN}/oauth/logout?client_id=${TEST_CLIENT_ID}${TEST_AUTHC_CLIENT_QUERY_STRING}`, ) }) it('calls `window.location.assign` with the correct url when `options.federated` is true', async () => { const authc = setup() authc.logout({ federated: true }) expect(window.location.assign).toHaveBeenCalledWith( `https://${TEST_DOMAIN}/oauth/logout?client_id=${TEST_CLIENT_ID}${TEST_AUTHC_CLIENT_QUERY_STRING}&federated`, ) }) it('calls `window.location.assign` with the correct url with custom `options.authcClient`', async () => { const authcClient = { name: '__test_client_name__', version: '9.9.9' } const authc = setup({ authcClient }) authc.logout() expectToHaveBeenCalledWithAuthcClientParam( window.location.assign, authcClient, ) }) it('clears the cache', async () => { const authc = setup() jest .spyOn(authc.cacheManager, 'clearSync') .mockReturnValueOnce(undefined) authc.logout() expect(authc.cacheManager.clearSync).toHaveBeenCalled() }) it('removes authenticated cookie from storage when `options.localOnly` is true', async () => { const authc = setup() authc.logout({ localOnly: true }) expect(esCookie.remove).toHaveBeenCalledWith( `authc.${TEST_CLIENT_ID}.is.authenticated`, ) }) it('removes the organization hint cookie from storage when `options.localOnly` is true', async () => { const authc = setup() authc.logout({ localOnly: true }) expect(esCookie.remove).toHaveBeenCalledWith( `authc.${TEST_CLIENT_ID}.organization_hint`, ) }) it('skips `window.location.assign` when `options.localOnly` is true', async () => { const authc = setup() authc.logout({ localOnly: true }) expect(window.location.assign).not.toHaveBeenCalledWith() }) it('calls `window.location.assign` when `options.localOnly` is false', async () => { const authc = setup() authc.logout({ localOnly: false }) expect(window.location.assign).toHaveBeenCalled() }) it('throws when both `options.localOnly` and `options.federated` are true', async () => { const authc = setup() const fn = () => authc.logout({ localOnly: true, federated: true }) expect(fn).toThrow() }) it('can access isAuthenticated immediately after local logout', async () => { const authc = setup() await loginWithRedirect(authc) expect(await authc.isAuthenticated()).toBe(true) authc.logout({ localOnly: true }) expect(await authc.isAuthenticated()).toBe(false) }) it('can access isAuthenticated immediately after local logout when using a custom async cache', async () => { const authc = setup({ cache: new InMemoryAsyncCacheNoKeys(), }) await loginWithRedirect(authc) expect(await authc.isAuthenticated()).toBe(true) await authc.logout({ localOnly: true }) expect(await authc.isAuthenticated()).toBe(false) }) }) })