Skip to content

Commit

Permalink
idx expired fix
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredperreault-okta committed May 17, 2022
1 parent 3f6c336 commit e131013
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 105 deletions.
15 changes: 14 additions & 1 deletion lib/idx/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import { IdxMessage, IdxResponse, isIdxResponse } from './types/idx-js';
import { getSavedTransactionMeta, saveTransactionMeta } from './transactionMeta';
import { getAvailableSteps, getEnabledFeatures, getMessagesFromResponse, isTerminalResponse } from './util';

declare interface RunData {
options: RunOptions;
values: remediators.RemediationValues;
Expand Down Expand Up @@ -257,9 +258,15 @@ async function finalizeData(authClient, data: RunData): Promise<RunData> {
const hasActions = Object.keys(idxResponse!.actions).length > 0;
const hasErrors = !!messages.find(msg => msg.class === 'ERROR');
const isTerminalSuccess = !hasActions && !hasErrors && idxResponse!.requestDidSucceed === true;
const isTerminalFailure = !hasActions && hasErrors && idxResponse!.requestDidSucceed === false;
if (isTerminalSuccess) {
shouldClearTransaction = true;
} else {
}
else if (isTerminalFailure) {
shouldClearTransaction = true;
status = IdxStatus.FAILURE;
}
else {
// only save response if there are actions available (ignore messages)
shouldSaveResponse = shouldSaveResponse && hasActions;
}
Expand All @@ -279,6 +286,12 @@ async function finalizeData(authClient, data: RunData): Promise<RunData> {
shouldClearTransaction = true;
}
}

if (status === IdxStatus.FAILURE) {
clearSharedStorage = true;
shouldClearTransaction = true;
}

return {
...data,
status,
Expand Down
226 changes: 123 additions & 103 deletions test/spec/idx/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('idx/run', () => {

const remediateResponse = {
idxResponse,
nextStep: 'remediate-nextStep'
// nextStep: 'remediate-nextStep'
};
jest.spyOn(mocked.remediate, 'remediate').mockResolvedValue(remediateResponse);

Expand Down Expand Up @@ -100,103 +100,12 @@ describe('idx/run', () => {
};
});

describe('with stateHandle', () => {
it('will call introspect', async () => {
const { authClient } = testContext;
const stateHandle = 'abc';
await run(authClient, { stateHandle });
expect(mocked.introspect.introspect).toHaveBeenCalledWith(authClient, {
withCredentials: true,
stateHandle
});
});

it('does not call interact', async () => {
const { authClient } = testContext;
const stateHandle = 'abc';
await run(authClient, { stateHandle });
expect(mocked.interact.interact).not.toHaveBeenCalled();
});

it('will preserve transaction meta, if it exists', async () => {
const { authClient, transactionMeta } = testContext;
jest.spyOn(mocked.transactionMeta, 'getSavedTransactionMeta').mockReturnValue(transactionMeta);
jest.spyOn(mocked.transactionMeta, 'saveTransactionMeta');
const stateHandle = 'abc';
await run(authClient, { stateHandle });
expect(mocked.transactionMeta.saveTransactionMeta).toHaveBeenCalledWith(authClient, transactionMeta);
});
});

describe('flow', () => {
it('if not specified or already set, sets the flow to "default"', async () => {
const { authClient } = testContext;
jest.spyOn(authClient.idx, 'setFlow');
await run(authClient);
expect(authClient.idx.setFlow).toHaveBeenCalledWith('default');
});

it('if flow is set in run options, it sets the flow on the authClient', async () => {
const { authClient } = testContext;
const flow = 'signup';
jest.spyOn(authClient.idx, 'setFlow');
await run(authClient, { flow });
expect(authClient.idx.setFlow).toHaveBeenCalledWith(flow);
});

it('if flow is not set in run options, it respects the flow already set on auth client', async () => {
const { authClient } = testContext;
jest.spyOn(authClient.idx, 'getFlow').mockReturnValue('existing');
jest.spyOn(authClient.idx, 'setFlow');
await run(authClient);
expect(authClient.idx.setFlow).toHaveBeenCalledWith('existing');
});

it('retrieves flow specification based on flow option', async () => {
const { authClient } = testContext;
jest.spyOn(mocked.FlowSpecification, 'getFlowSpecification');
await run(authClient, { flow: 'signup' });
expect(mocked.FlowSpecification.getFlowSpecification).toHaveBeenCalledWith(authClient, 'signup');
});
});

describe('with saved transaction', () => {
beforeEach(() => {
const { transactionMeta } = testContext;
jest.spyOn(mocked.transactionMeta, 'getSavedTransactionMeta').mockReturnValue(transactionMeta);
});
it('if saved meta has no interactionHandle, will call interact', async () => {
const { authClient } = testContext;
await run(authClient);
expect(mocked.interact.interact).toHaveBeenCalled();
});
it('if saved meta has interactionHandle, does not call interact', async () => {
const { authClient, transactionMeta } = testContext;
transactionMeta.interactionHandle = 'fake';
await run(authClient);
expect(mocked.interact.interact).not.toHaveBeenCalled();
});
});

describe('no saved transaction', () => {
beforeEach(() => {
jest.spyOn(mocked.transactionMeta, 'getSavedTransactionMeta').mockReturnValue(undefined);
});
it('clears saved transaction data and calls interact', async () => {
const { authClient } = testContext;
jest.spyOn(authClient.transactionManager, 'clear');
await run(authClient);
expect(authClient.transactionManager.clear).toHaveBeenCalledTimes(1);
expect(mocked.interact.interact).toHaveBeenCalled();
});
});

it('returns transaction', async () => {
const { authClient } = testContext;
const res = await run(authClient);
expect(res).toMatchObject({
status: IdxStatus.PENDING,
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
requestDidSucceed: true
});
});
Expand Down Expand Up @@ -318,11 +227,122 @@ describe('idx/run', () => {
const res = await run(authClient);
expect(res).toMatchObject({
messages: ['remediate-message-1'],
nextStep: 'remediate-nextStep',
status: IdxStatus.PENDING,
});
});

describe('with stateHandle', () => {
it('will call introspect', async () => {
const { authClient } = testContext;
const stateHandle = 'abc';
await run(authClient, { stateHandle });
expect(mocked.introspect.introspect).toHaveBeenCalledWith(authClient, {
withCredentials: true,
stateHandle
});
});

it('does not call interact', async () => {
const { authClient } = testContext;
const stateHandle = 'abc';
await run(authClient, { stateHandle });
expect(mocked.interact.interact).not.toHaveBeenCalled();
});

it('will preserve transaction meta, if it exists', async () => {
const { authClient, transactionMeta } = testContext;
jest.spyOn(mocked.transactionMeta, 'getSavedTransactionMeta').mockReturnValue(transactionMeta);
jest.spyOn(mocked.transactionMeta, 'saveTransactionMeta');
const stateHandle = 'abc';
await run(authClient, { stateHandle });
expect(mocked.transactionMeta.saveTransactionMeta).toHaveBeenCalledWith(authClient, transactionMeta);
});
});

describe('flow', () => {
it('if not specified or already set, sets the flow to "default"', async () => {
const { authClient } = testContext;
jest.spyOn(authClient.idx, 'setFlow');
await run(authClient);
expect(authClient.idx.setFlow).toHaveBeenCalledWith('default');
});

it('if flow is set in run options, it sets the flow on the authClient', async () => {
const { authClient } = testContext;
const flow = 'signup';
jest.spyOn(authClient.idx, 'setFlow');
await run(authClient, { flow });
expect(authClient.idx.setFlow).toHaveBeenCalledWith(flow);
});

it('if flow is not set in run options, it respects the flow already set on auth client', async () => {
const { authClient } = testContext;
jest.spyOn(authClient.idx, 'getFlow').mockReturnValue('existing');
jest.spyOn(authClient.idx, 'setFlow');
await run(authClient);
expect(authClient.idx.setFlow).toHaveBeenCalledWith('existing');
});

it('retrieves flow specification based on flow option', async () => {
const { authClient } = testContext;
jest.spyOn(mocked.FlowSpecification, 'getFlowSpecification');
await run(authClient, { flow: 'signup' });
expect(mocked.FlowSpecification.getFlowSpecification).toHaveBeenCalledWith(authClient, 'signup');
});
});

describe('with saved transaction', () => {
beforeEach(() => {
const { transactionMeta } = testContext;
jest.spyOn(mocked.transactionMeta, 'getSavedTransactionMeta').mockReturnValue(transactionMeta);
});
it('if saved meta has no interactionHandle, will call interact', async () => {
const { authClient } = testContext;
await run(authClient);
expect(mocked.interact.interact).toHaveBeenCalled();
});
it('if saved meta has interactionHandle, does not call interact', async () => {
const { authClient, transactionMeta } = testContext;
transactionMeta.interactionHandle = 'fake';
await run(authClient);
expect(mocked.interact.interact).not.toHaveBeenCalled();
});

it('if saved meta has interactionHandle and introspect fails, clears transaction', async () => {
const { authClient, transactionMeta } = testContext;
transactionMeta.interactionHandle = 'fake';
const expiredInteractionHandleResponse = IdxResponseFactory.build({
neededToProceed: [],
requestDidSucceed: false,
rawIdxState: {
messages: {
value: [{class: 'ERROR', i18n: { key: 'idx.session.expired' }, message: 'foo'}]
}
}
});
jest.spyOn(mocked.introspect, 'introspect').mockResolvedValue(expiredInteractionHandleResponse);
jest.spyOn(mocked.remediate, 'remediate').mockResolvedValue({idxResponse: expiredInteractionHandleResponse});
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(mocked.interact.interact).not.toHaveBeenCalled();
expect(authClient.transactionManager.clear).toHaveBeenCalled();
expect(res.status).toBe(IdxStatus.FAILURE);
});
});

describe('no saved transaction', () => {
beforeEach(() => {
jest.spyOn(mocked.transactionMeta, 'getSavedTransactionMeta').mockReturnValue(undefined);
});
it('clears saved transaction data and calls interact', async () => {
const { authClient } = testContext;
jest.spyOn(authClient.transactionManager, 'clear');
await run(authClient);
expect(authClient.transactionManager.clear).toHaveBeenCalledTimes(1);
expect(mocked.interact.interact).toHaveBeenCalled();
});
});

describe('response is not terminal', () => {
beforeEach(() => {
const { transactionMeta } = testContext;
Expand All @@ -338,7 +358,7 @@ describe('idx/run', () => {
const res = await run(authClient);
expect(authClient.transactionManager.clear).not.toHaveBeenCalledWith();
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.PENDING,
});
});
Expand Down Expand Up @@ -384,7 +404,7 @@ describe('idx/run', () => {
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.PENDING,
});
expect(authClient.transactionManager.clear).not.toHaveBeenCalled();
Expand Down Expand Up @@ -445,7 +465,7 @@ describe('idx/run', () => {
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.TERMINAL,
});
expect(authClient.transactionManager.clear).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -476,7 +496,7 @@ describe('idx/run', () => {
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.TERMINAL,
});
expect(authClient.transactionManager.clear).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -510,7 +530,7 @@ describe('idx/run', () => {
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.TERMINAL,
});
expect(authClient.transactionManager.clear).not.toHaveBeenCalled();
Expand Down Expand Up @@ -539,7 +559,7 @@ describe('idx/run', () => {
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.TERMINAL,
});
expect(authClient.transactionManager.clear).not.toHaveBeenCalled();
Expand Down Expand Up @@ -579,7 +599,7 @@ describe('idx/run', () => {
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.TERMINAL,
});
expect(authClient.transactionManager.clear).not.toHaveBeenCalled();
Expand Down Expand Up @@ -611,7 +631,7 @@ describe('idx/run', () => {
jest.spyOn(authClient.transactionManager, 'clear');
const res = await run(authClient);
expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.TERMINAL,
});
expect(authClient.transactionManager.clear).not.toHaveBeenCalled();
Expand Down Expand Up @@ -655,7 +675,7 @@ describe('idx/run', () => {
});

expect(res).toMatchObject({
nextStep: 'remediate-nextStep',
rawIdxState: expect.any(Object),
status: IdxStatus.SUCCESS,
tokens: tokenResponse.tokens,
});
Expand Down
3 changes: 2 additions & 1 deletion test/support/idx/factories/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export const IdxResponseFactory = Factory.define<IdxResponse, MockedIdxResponseT
}),
actions: {},
toPersist: {},
context: {} as IdxContext
context: {} as IdxContext,
requestDidSucceed: true
};
});

Expand Down

0 comments on commit e131013

Please sign in to comment.