Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: removes interactionHandle from meta when introspect fails #1211

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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