ts/store/reducers/__tests__/identification.test.ts
import * as AR from "fp-ts/lib/Array";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import { PinString } from "../../../types/PinString";
import { reproduceSequence } from "../../../utils/tests";
import {
identificationCancel,
identificationFailure,
identificationForceLogout,
identificationReset,
identificationStart,
identificationSuccess
} from "../../actions/identification";
import { Action } from "../../actions/types";
import reducer, {
deltaTimespanBetweenAttempts,
freeAttempts,
IdentificationState,
maxAttempts
} from "../identification";
const identificationStartMock = identificationStart("111111" as PinString);
describe("Identification reducer", () => {
it("should return the initial state", () => {
const result = reducer(undefined, identificationStartMock);
expect(result.progress.kind).toEqual("started");
expect(result.fail).toEqual(undefined);
});
it("should return correct state after identification success", () => {
const startState = reducer(undefined, identificationStartMock);
const successState = reducer(
startState,
identificationSuccess({ isBiometric: false })
);
expect(successState.progress.kind).toEqual("identified");
expect(successState.fail).toEqual(undefined);
});
it("should return correct state after the first identification fail", () => {
const sequenceOfActions: ReadonlyArray<Action> = [
identificationStartMock,
identificationFailure()
];
const finalState = reproduceSequence(
{} as IdentificationState,
reducer,
sequenceOfActions
);
expect(finalState.progress.kind).toEqual("started");
expectFailState(finalState, maxAttempts - 1, 0);
});
it("should return correct state after the complete sequence of fails", () => {
const initialState = reducer(undefined, identificationStartMock);
expectFailSequence(initialState);
});
it("should return correct state after a complete sequence of fails followed by another fail action (overflow)", () => {
const initialState = reducer(undefined, identificationStartMock);
const finalState = expectFailSequence(initialState);
// check what happens in the event of an overflow of action
const overflow = reducer(finalState, identificationFailure());
expectFailState(
overflow,
1,
(maxAttempts - freeAttempts - 1) * deltaTimespanBetweenAttempts
);
});
it("should return correct state after a complete sequence of fails followed by another action", () => {
const initialState = reducer(undefined, identificationStartMock);
const failState = expectFailSequence(initialState);
// expect to reset the fail state when the next action is used
const expectFailStateReset = (nextAction: Action, expectedKind: string) => {
const nextState = reducer(failState, nextAction);
expect(nextState.progress.kind).toEqual(expectedKind);
expect(nextState.fail).toEqual(undefined);
};
// expect to keep the fail state when the next action is used
const expectFailStateKeep = (nextAction: Action, expectedKind: string) => {
const nextState = reducer(failState, nextAction);
expect(nextState.progress.kind).toEqual(expectedKind);
expectFailState(
nextState,
1,
(maxAttempts - freeAttempts - 1) * deltaTimespanBetweenAttempts
);
};
// after a success the fail state is cleared
expectFailStateReset(
identificationSuccess({ isBiometric: false }),
"identified"
);
// after a reset the fail state is cleared
expectFailStateReset(identificationReset(), "unidentified");
// after an identificationstart the fail state is keep
expectFailStateKeep(identificationStartMock, "started");
// after an identificationcancel the fail state is keep
expectFailStateKeep(identificationCancel(), "unidentified");
// expect no change with a not involved action
expectFailStateKeep(identificationForceLogout(), "started");
});
it("should return correct fail state after starting the complete failing sequence from different states", () => {
const identificationResetState = reducer(undefined, identificationReset());
expectFailSequence(identificationResetState);
const expectFailSequenceFromStartingState = (action: Action) => {
expectFailSequence(reducer(identificationResetState, action));
};
// start the full identification sequence from different states
[
identificationCancel(),
identificationSuccess({ isBiometric: false }),
identificationStartMock
].forEach(action => expectFailSequenceFromStartingState(action));
});
it("should execute multiple fail sequence after a reset of the fail state correctly", () => {
const identificationResetState = reducer(undefined, identificationReset());
pipe(
identificationResetState,
expectFailSequence,
(state: IdentificationState) =>
reducer(state, identificationSuccess({ isBiometric: false })),
expectFailSequence,
(state: IdentificationState) => reducer(state, identificationReset()),
expectFailSequence
);
});
});
/**
* This function execute the full fail sequence, simulate the insertion of the wrong pin for
* :maxAttempts -1: amount of time.
* @param initialState
*/
const expectFailSequence = (
initialState: IdentificationState
): IdentificationState => {
const sequenceOfActions: ReadonlyArray<Action> = AR.range(
1,
maxAttempts - 1
).map(_ => identificationFailure());
const expectedTimespan = AR.range(1, freeAttempts)
.map(_ => 0)
.concat(
AR.range(1, maxAttempts - freeAttempts).map(
i => i * deltaTimespanBetweenAttempts
)
);
const finalState = sequenceOfActions.reduce((acc, val, i) => {
const newState = reducer(acc, val);
expectFailState(newState, maxAttempts - i - 1, expectedTimespan[i]);
return newState;
}, initialState);
// check the final state after all the attempts
expectFailState(
finalState,
1,
(maxAttempts - freeAttempts - 1) * deltaTimespanBetweenAttempts
);
return finalState;
};
/**
* Verify if a IdentificationState satisfies all the properties for a fail condition
* @param state
* @param expectedRemainingAttempts
* @param expectedTimeSpan
*/
const expectFailState = (
state: IdentificationState,
expectedRemainingAttempts: number,
expectedTimeSpan: number
) => {
expect(state.fail).toBeDefined();
pipe(
state.fail,
O.fromNullable,
O.map(failState => {
expect(failState.remainingAttempts).toEqual(expectedRemainingAttempts);
expect(failState.timespanBetweenAttempts).toEqual(expectedTimeSpan);
})
);
};