@thoughtspot/visual-embed-sdk
Version:
ThoughtSpot Embed SDK
554 lines • 25.9 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.mockSessionInfoApiResponse = exports.embedConfig = void 0;
const tslib_1 = require("tslib");
require("jest-fetch-mock");
const authInstance = tslib_1.__importStar(require("./auth"));
const authTokenService = tslib_1.__importStar(require("./authToken"));
const EmbedConfig = tslib_1.__importStar(require("./embed/embedConfig"));
const mixPanelService = tslib_1.__importStar(require("./mixpanel-service"));
const test_utils_1 = require("./test/test-utils");
const types_1 = require("./types");
const checkReleaseVersionInBetaInstance = tslib_1.__importStar(require("./utils"));
const authService = tslib_1.__importStar(require("./utils/authService/authService"));
const tokenAuthService = tslib_1.__importStar(require("./utils/authService/tokenizedAuthService"));
const logger_1 = require("./utils/logger");
const SessionService = tslib_1.__importStar(require("./utils/sessionInfoService"));
const thoughtSpotHost = 'http://localhost:3000';
const username = 'tsuser';
const password = '12345678';
const samalLoginUrl = `${thoughtSpotHost}/callosum/v1/saml/login?targetURLPath=%23%3FtsSSOMarker%3D5e16222e-ef02-43e9-9fbd-24226bf3ce5b`;
exports.embedConfig = {
doTokenAuthSuccess: (token) => ({
thoughtSpotHost,
username,
authEndpoint: 'auth',
authType: types_1.AuthType.TrustedAuthToken,
getAuthToken: jest.fn(() => Promise.resolve(token)),
}),
doTokenAuthWithCookieDetect: {
thoughtSpotHost,
username,
authEndpoint: 'auth',
detectCookieAccessSlow: true,
},
doTokenAuthFailureWithoutAuthEndPoint: {
thoughtSpotHost,
username,
authEndpoint: '',
getAuthToken: null,
},
doTokenAuthFailureWithoutGetAuthToken: {
thoughtSpotHost,
username,
authEndpoint: 'auth',
getAuthToken: null,
},
doBasicAuth: {
thoughtSpotHost,
username,
password,
},
doSamlAuth: {
thoughtSpotHost,
},
doSamlAuthNoRedirect: {
thoughtSpotHost,
inPopup: true,
authTriggerContainer: document.body,
authTriggerText: 'auth',
},
doOidcAuth: {
thoughtSpotHost,
},
SSOAuth: {
authType: types_1.AuthType.SSO,
},
SAMLAuth: {
authType: types_1.AuthType.SAML,
},
OIDCAuth: {
authType: types_1.AuthType.OIDC,
},
authServerFailure: {
thoughtSpotHost,
username,
authEndpoint: '',
getAuthToken: null,
authType: types_1.AuthType.AuthServer,
},
authServerCookielessFailure: {
thoughtSpotHost,
username,
authEndpoint: '',
getAuthToken: null,
authType: types_1.AuthType.TrustedAuthTokenCookieless,
},
basicAuthSuccess: {
thoughtSpotHost,
username,
password,
authType: types_1.AuthType.Basic,
},
nonAuthSucess: {
thoughtSpotHost,
username,
password,
authType: types_1.AuthType.None,
},
doCookielessAuth: (token) => ({
thoughtSpotHost,
username,
authType: types_1.AuthType.TrustedAuthTokenCookieless,
getAuthToken: jest.fn(() => Promise.resolve(token)),
}),
};
const originalWindow = window;
exports.mockSessionInfoApiResponse = {
userGUID: '1234',
releaseVersion: 'test',
configInfo: {
isPublicUser: false,
mixpanelConfig: {
production: true,
devSdkKey: 'devKey',
prodSdkKey: 'prodKey',
},
},
};
describe('Unit test for auth', () => {
beforeEach(() => {
jest.resetAllMocks();
global.fetch = window.fetch;
});
afterEach(() => {
authTokenService.resetCachedAuthToken();
SessionService.resetCachedSessionInfo();
jest.resetAllMocks();
});
test('endpoints, SAML_LOGIN_TEMPLATE', () => {
const ssoTemplateUrl = authService.EndPoints.SAML_LOGIN_TEMPLATE(thoughtSpotHost);
expect(ssoTemplateUrl).toBe(`/callosum/v1/saml/login?targetURLPath=${thoughtSpotHost}`);
});
test('when session info giving response, it is cached', async () => {
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockResolvedValueOnce(exports.mockSessionInfoApiResponse);
const sessionInfo = await SessionService.getSessionInfo();
expect(sessionInfo.mixpanelToken).toEqual('prodKey');
expect(sessionInfo.isPublicUser).toEqual(false);
await SessionService.getSessionInfo();
const cachedInfo = SessionService.getCachedSessionInfo();
expect(cachedInfo).toEqual(sessionInfo);
expect(tokenAuthService.fetchSessionInfoService).toHaveBeenCalledTimes(1);
});
test('Disable mixpanel when disableSDKTracking flag is set', () => {
jest.spyOn(mixPanelService, 'initMixpanel');
jest.spyOn(SessionService, 'getSessionInfo').mockReturnValue(test_utils_1.mockSessionInfo);
jest.spyOn(EmbedConfig, 'getEmbedConfig').mockReturnValue({ disableSDKTracking: true });
authInstance.postLoginService();
expect(mixPanelService.initMixpanel).not.toBeCalled();
});
test('Log error is postLogin faild', async () => {
jest.spyOn(mixPanelService, 'initMixpanel');
jest.spyOn(SessionService, 'getSessionInfo').mockRejectedValueOnce(test_utils_1.mockSessionInfo);
jest.spyOn(EmbedConfig, 'getEmbedConfig').mockReturnValue({ disableSDKTracking: true });
jest.spyOn(logger_1.logger, 'error').mockResolvedValue(true);
await authInstance.postLoginService();
expect(mixPanelService.initMixpanel).not.toBeCalled();
expect(logger_1.logger.error).toBeCalled();
});
test('doCookielessTokenAuth: when authEndpoint and getAuthToken are not there, it throw error', async () => {
try {
await authInstance.doCookielessTokenAuth(exports.embedConfig.doTokenAuthFailureWithoutAuthEndPoint);
}
catch (e) {
expect(e.message).toBe('Either auth endpoint or getAuthToken function must be provided');
}
});
test('doTokenAuth: when authEndpoint and getAuthToken are not there, it throw error', async () => {
try {
await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthFailureWithoutAuthEndPoint);
}
catch (e) {
expect(e.message).toBe('Either auth endpoint or getAuthToken function must be provided');
}
});
test('doTokenAuth: when user is loggedIn', async () => {
const getAuthenticationTokenMock = jest.spyOn(authTokenService, 'getAuthenticationToken');
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => true);
await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthSuccess('authToken'));
expect(authTokenService.getAuthenticationToken).not.toBeCalled();
expect(authInstance.loggedInStatus).toBe(true);
getAuthenticationTokenMock.mockRestore();
});
test('doTokenAuth: when user is not loggedIn & getAuthToken have response', async () => {
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => false);
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() => Promise.resolve({
status: 200,
ok: true,
}));
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthSuccess('authToken2'));
expect(authService.fetchAuthService).toBeCalledWith(thoughtSpotHost, username, 'authToken2');
});
test('doTokenAuth: when user is not loggedIn & getAuthToken not present, isLoggedIn should called', async () => {
fetchMock.mockResponse(JSON.stringify({ mixpanelAccessToken: '' }));
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => false);
jest.spyOn(authService, 'fetchAuthTokenService').mockImplementation(() => ({
text: () => Promise.resolve('abc'),
}));
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() => Promise.resolve({
status: 200,
ok: true,
}));
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthFailureWithoutGetAuthToken);
expect(authService.fetchAuthTokenService).toBeCalledWith('auth');
await (0, test_utils_1.executeAfterWait)(() => {
expect(authInstance.loggedInStatus).toBe(true);
expect(authService.fetchAuthService).toBeCalledWith(thoughtSpotHost, username, 'abc');
});
});
test('doTokenAuth: Should raise error when duplicate token is used', async () => {
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => false);
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockResolvedValue({
status: 401,
});
jest.spyOn(window, 'alert').mockClear();
jest.spyOn(window, 'alert').mockReturnValue(undefined);
jest.spyOn(authService, 'fetchAuthService').mockReset();
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() => Promise.resolve({
status: 200,
ok: true,
}));
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthSuccess('authToken3'));
try {
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(false);
await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthSuccess('authToken3'));
expect(false).toBe(true);
}
catch (e) {
expect(e.message).toContain('Duplicate token');
}
await (0, test_utils_1.executeAfterWait)(() => {
expect(authInstance.loggedInStatus).toBe(false);
expect(window.alert).toBeCalled();
expect(authService.fetchAuthService).toHaveBeenCalledTimes(1);
});
});
test('doTokenAuth: Should set loggedInStatus if detectThirdPartyCookieAccess is true and the second info call fails', async () => {
jest.spyOn(tokenAuthService, 'fetchSessionInfoService')
.mockResolvedValue({
status: 401,
})
.mockClear();
jest.spyOn(authService, 'fetchAuthTokenService').mockImplementation(() => ({
text: () => Promise.resolve('abc'),
}));
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() => Promise.resolve({
status: 200,
ok: true,
}));
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
jest.spyOn(tokenAuthService, 'isActiveService').mockResolvedValueOnce(false);
jest.spyOn(tokenAuthService, 'isActiveService').mockResolvedValueOnce(false);
const isLoggedIn = await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthWithCookieDetect);
expect(tokenAuthService.isActiveService).toHaveBeenCalledTimes(2);
expect(isLoggedIn).toBe(false);
});
test('doTokenAuth: when user is not loggedIn & fetchAuthPostService failed than fetchAuthService should call', async () => {
jest.spyOn(window, 'alert').mockImplementation(() => undefined);
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => false);
jest.spyOn(authService, 'fetchAuthTokenService').mockImplementation(() => ({
text: () => Promise.resolve('abc'),
}));
jest.spyOn(authService, 'fetchAuthPostService').mockImplementation(() =>
// eslint-disable-next-line prefer-promise-reject-errors, implicit-arrow-linebreak
Promise.reject({
status: 500,
}));
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() => Promise.resolve({
status: 200,
type: 'opaqueredirect',
}));
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
expect(await authInstance.doTokenAuth(exports.embedConfig.doTokenAuthSuccess('authToken2'))).toBe(true);
expect(authService.fetchAuthPostService).toBeCalledWith(thoughtSpotHost, username, 'authToken2');
expect(authService.fetchAuthService).toBeCalledWith(thoughtSpotHost, username, 'authToken2');
});
describe('doBasicAuth', () => {
beforeEach(() => {
global.fetch = window.fetch;
});
it('when user is loggedIn', async () => {
jest.spyOn(tokenAuthService, 'isActiveService').mockResolvedValueOnce(true);
await authInstance.doBasicAuth(exports.embedConfig.doBasicAuth);
expect(authInstance.loggedInStatus).toBe(true);
});
it('when user is not loggedIn', async () => {
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
jest.spyOn(authService, 'fetchBasicAuthService').mockImplementation(() => ({
status: 200,
ok: true,
}));
await authInstance.doBasicAuth(exports.embedConfig.doBasicAuth);
// expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
expect(authService.fetchBasicAuthService).toBeCalled();
expect(authInstance.loggedInStatus).toBe(true);
});
});
describe('doSamlAuth', () => {
afterEach(() => {
delete global.window;
global.window = Object.create(originalWindow);
global.window.open = jest.fn();
global.fetch = window.fetch;
});
it('when user is loggedIn & isAtSSORedirectUrl is true', async () => {
spyOn(checkReleaseVersionInBetaInstance, 'checkReleaseVersionInBeta');
Object.defineProperty(window, 'location', {
value: {
href: `asd.com#?tsSSOMarker=${authInstance.SSO_REDIRECTION_MARKER_GUID}`,
hash: `?tsSSOMarker=${authInstance.SSO_REDIRECTION_MARKER_GUID}`,
},
});
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(async () => ({
json: () => test_utils_1.mockSessionInfo,
status: 200,
}));
jest.spyOn(tokenAuthService, 'isActiveService').mockReturnValue(true);
await authInstance.doSamlAuth(exports.embedConfig.doSamlAuth);
expect(window.location.hash).toBe('');
expect(authInstance.loggedInStatus).toBe(true);
});
it('when user is not loggedIn & isAtSSORedirectUrl is true', async () => {
Object.defineProperty(window, 'location', {
value: {
href: `asd.com#?tsSSOMarker=${authInstance.SSO_REDIRECTION_MARKER_GUID}`,
hash: `?tsSSOMarker=${authInstance.SSO_REDIRECTION_MARKER_GUID}`,
},
});
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
await authInstance.doSamlAuth(exports.embedConfig.doSamlAuth);
expect(window.location.hash).toBe('');
expect(authInstance.loggedInStatus).toBe(false);
});
it('when user is not loggedIn, in config noRedirect is false and isAtSSORedirectUrl is false', async () => {
Object.defineProperty(window, 'location', {
value: {
href: '',
hash: '',
},
});
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
await authInstance.doSamlAuth(exports.embedConfig.doSamlAuth);
expect(global.window.location.href).toBe(samalLoginUrl);
});
it('should emit SAML_POPUP_CLOSED_NO_AUTH when popup window is closed', async () => {
jest.useFakeTimers();
const mockPopupWindow = {
closed: false,
focus: jest.fn(),
close: jest.fn()
};
global.window.open = jest.fn().mockReturnValue(mockPopupWindow);
Object.defineProperty(window, 'location', {
value: {
href: '',
hash: '',
},
});
spyOn(authInstance, 'samlCompletionPromise').and.returnValue(Promise.resolve(false));
const emitSpy = jest.fn();
const mockEventEmitter = {
emit: emitSpy,
once: jest.fn(),
on: jest.fn()
};
authInstance.setAuthEE(mockEventEmitter);
jest.spyOn(tokenAuthService, 'isActiveService')
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
expect(await authInstance.doSamlAuth({
...exports.embedConfig.doSamlAuthNoRedirect,
})).toBe(true);
document.getElementById('ts-auth-btn').click();
mockPopupWindow.closed = true;
jest.advanceTimersByTime(1000);
window.postMessage({ type: types_1.EmbedEvent.SAMLComplete }, '*');
await authInstance.samlCompletionPromise;
expect(emitSpy).toHaveBeenCalledWith(authInstance.AuthStatus.SAML_POPUP_CLOSED_NO_AUTH);
jest.useRealTimers();
authInstance.setAuthEE(null);
});
it('when user is not loggedIn, in config noRedirect is true and isAtSSORedirectUrl is false', async () => {
Object.defineProperty(window, 'location', {
value: {
href: '',
hash: '',
},
});
spyOn(authInstance, 'samlCompletionPromise');
global.window.open = jest.fn();
jest.spyOn(tokenAuthService, 'isActiveService')
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
expect(await authInstance.samlCompletionPromise).not.toBe(null);
expect(await authInstance.doSamlAuth({
...exports.embedConfig.doSamlAuthNoRedirect,
})).toBe(true);
document.getElementById('ts-auth-btn').click();
window.postMessage({ type: types_1.EmbedEvent.SAMLComplete }, '*');
await authInstance.samlCompletionPromise;
expect(authInstance.loggedInStatus).toBe(true);
});
it('should support emitting SAML_POPUP_CLOSED_NO_AUTH event', () => {
const emitSpy = jest.fn();
const mockEventEmitter = {
emit: emitSpy,
once: jest.fn()
};
authInstance.setAuthEE(mockEventEmitter);
authInstance.getAuthEE().emit(authInstance.AuthStatus.SAML_POPUP_CLOSED_NO_AUTH);
expect(emitSpy).toHaveBeenCalledWith(authInstance.AuthStatus.SAML_POPUP_CLOSED_NO_AUTH);
authInstance.setAuthEE(null);
});
});
describe('doOIDCAuth', () => {
afterEach(() => {
authTokenService.resetCachedAuthToken();
delete global.window;
global.window = Object.create(originalWindow);
global.window.open = jest.fn();
global.fetch = window.fetch;
});
it('when user is not loggedIn & isAtSSORedirectUrl is true', async () => {
Object.defineProperty(window, 'location', {
value: {
href: `asd.com#?tsSSOMarker=${authInstance.SSO_REDIRECTION_MARKER_GUID}`,
hash: `?tsSSOMarker=${authInstance.SSO_REDIRECTION_MARKER_GUID}`,
},
});
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
await authInstance.doOIDCAuth(exports.embedConfig.doOidcAuth);
expect(window.location.hash).toBe('');
expect(authInstance.loggedInStatus).toBe(false);
});
});
it('authenticate: when authType is SSO', async () => {
jest.spyOn(authInstance, 'doSamlAuth');
await authInstance.authenticate(exports.embedConfig.SSOAuth);
expect(window.location.hash).toBe('');
expect(authInstance.doSamlAuth).toBeCalled();
});
it('authenticate: when authType is SMAL', async () => {
jest.spyOn(authInstance, 'doSamlAuth');
await authInstance.authenticate(exports.embedConfig.SAMLAuth);
expect(window.location.hash).toBe('');
expect(authInstance.doSamlAuth).toBeCalled();
});
it('authenticate: when authType is OIDC', async () => {
jest.spyOn(authInstance, 'doOIDCAuth');
await authInstance.authenticate(exports.embedConfig.OIDCAuth);
expect(window.location.hash).toBe('');
expect(authInstance.doOIDCAuth).toBeCalled();
});
it('authenticate: when authType is AuthServer', async () => {
spyOn(authInstance, 'doTokenAuth');
await authInstance.authenticate(exports.embedConfig.authServerFailure);
expect(window.location.hash).toBe('');
expect(authInstance.doTokenAuth).toBeCalled();
});
it('authenticate: when authType is AuthServerCookieless', async () => {
spyOn(authInstance, 'doCookielessTokenAuth');
await authInstance.authenticate(exports.embedConfig.authServerCookielessFailure);
expect(window.location.hash).toBe('');
expect(authInstance.doCookielessTokenAuth).toBeCalled();
});
it('authenticate: when authType is Basic', async () => {
jest.spyOn(authInstance, 'doBasicAuth');
jest.spyOn(authService, 'fetchBasicAuthService').mockImplementation(() => Promise.resolve({ status: 200, ok: true }));
await authInstance.authenticate(exports.embedConfig.basicAuthSuccess);
expect(authInstance.doBasicAuth).toBeCalled();
expect(authInstance.loggedInStatus).toBe(true);
});
it('authenticate: when authType is None', async () => {
expect(await authInstance.authenticate(exports.embedConfig.nonAuthSucess)).not.toBeInstanceOf(Error);
});
it('user is authenticated when loggedInStatus is true', () => {
expect(authInstance.isAuthenticated()).toBe(authInstance.loggedInStatus);
});
it('doCookielessTokenAuth should resolve to true if valid token is passed', async () => {
jest.clearAllMocks();
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
const isLoggedIn = await authInstance.doCookielessTokenAuth(exports.embedConfig.doCookielessAuth('testToken'));
expect(isLoggedIn).toBe(true);
});
it('doCookielessTokenAuth should resolve to false if valid token is not passed', async () => {
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(false);
const isLoggedIn = await authInstance.doCookielessTokenAuth(exports.embedConfig.doCookielessAuth('testToken'));
expect(isLoggedIn).toBe(false);
});
it('get AuthEE should return proper value', () => {
const testObject = { test: 'true' };
authInstance.setAuthEE(testObject);
expect(authInstance.getAuthEE()).toBe(testObject);
});
it('getSessionDetails returns the correct details given sessionInfo', async () => {
jest.clearAllMocks();
jest.restoreAllMocks();
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockReturnValue({
userGUID: '1234',
releaseVersion: '1',
configInfo: {
mixpanelConfig: {
devSdkKey: 'devKey',
prodSdkKey: 'prodKey',
production: false,
},
},
});
const details = await SessionService.getSessionInfo();
expect(details).toEqual(expect.objectContaining({
mixpanelToken: 'devKey',
}));
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockReturnValue({
configInfo: {
mixpanelConfig: {
devSdkKey: 'devKey',
prodSdkKey: 'prodKey',
production: true,
},
},
});
SessionService.resetCachedSessionInfo();
const details2 = await SessionService.getSessionInfo();
expect(details2).toEqual(expect.objectContaining({
mixpanelToken: 'prodKey',
}));
});
test('notifyAuthSuccess if getSessionInfo returns data', async () => {
const dummyInfo = { test: 'dummy' };
jest.spyOn(SessionService, 'getSessionInfo').mockResolvedValueOnce(dummyInfo);
jest.spyOn(logger_1.logger, 'error').mockResolvedValueOnce(true);
const emitSpy = jest.fn();
authInstance.setAuthEE({ emit: emitSpy });
await authInstance.notifyAuthSuccess();
expect(logger_1.logger.error).not.toBeCalled();
expect(emitSpy).toBeCalledWith(authInstance.AuthStatus.SUCCESS, dummyInfo);
authInstance.setAuthEE(null);
});
test('notifyAuthSuccess if getSessionInfo fails', async () => {
jest.spyOn(SessionService, 'getSessionInfo').mockImplementation(() => {
throw new Error('error');
});
jest.spyOn(logger_1.logger, 'error');
const emitSpy = jest.fn();
authInstance.setAuthEE({ emit: emitSpy });
await authInstance.notifyAuthSuccess();
expect(logger_1.logger.error).toBeCalled();
expect(emitSpy).not.toBeCalled();
authInstance.setAuthEE(null);
});
});
//# sourceMappingURL=auth.spec.js.map
;