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

[#173338803] Integration test for activation phase #1931

Merged
merged 9 commits into from
Jun 23, 2020
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Either, right } from "fp-ts/lib/Either";
import { fromNullable, some } from "fp-ts/lib/Option";
import { Errors } from "io-ts";
import { Action, combineReducers } from "redux";
import { expectSaga } from "redux-saga-test-plan";
import * as matchers from "redux-saga-test-plan/matchers";
import { select } from "redux-saga-test-plan/matchers";
import { navigateToWalletHome } from "../../../../../../../store/actions/navigation";
import { navigationHistoryPop } from "../../../../../../../store/actions/navigationHistory";
import { navigationCurrentRouteSelector } from "../../../../../../../store/reducers/navigation";
import BONUSVACANZE_ROUTES from "../../../../../navigation/routes";
import {
cancelBonusRequest,
completeBonusVacanzeActivation
} from "../../../../actions/bonusVacanze";
import allActiveReducer from "../../../../reducers/allActive";
import { bonusActivationSaga } from "../../getBonusActivationSaga";
import { handleBonusActivationSaga } from "../../handleBonusActivationSaga";
import bonusVacanzeActivationReducer, {
BonusActivationProgressEnum
} from "./../../../../reducers/activation";
import {
ActivationBackendResponse,
backendIntegrationTestCases,
MockActivationState
} from "./mockData";

jest.mock("react-native-background-timer", () => {
return {
startTimer: jest.fn()
};
});

jest.mock("react-native-share", () => {
return {
open: jest.fn()
};
});

const activationReducer = combineReducers<MockActivationState, Action>({
activation: bonusVacanzeActivationReducer,
allActive: allActiveReducer
});

const getDisplayNameBackendResponse = (value: Either<Errors, any>): string => {
return value.fold(
_ => {
return "Left error";
},
r => {
return r ? (r.status as string) : "undefined";
}
);
};

describe("Bonus Activation Saga Integration Test", () => {
it("Cancel A bonus request after server error", () => {
const startBonusActivation = jest.fn();
const getActivationById = jest.fn();

return expectSaga(
handleBonusActivationSaga,
bonusActivationSaga(startBonusActivation, getActivationById)
)
.withReducer(activationReducer)
.provide([
[
select(navigationCurrentRouteSelector),
some(BONUSVACANZE_ROUTES.ACTIVATION.LOADING)
],
[
matchers.call.fn(startBonusActivation),
Undermaken marked this conversation as resolved.
Show resolved Hide resolved
right({ status: 500, value: {} })
]
])
.dispatch(cancelBonusRequest())
.put(navigationHistoryPop(1))
.put(navigateToWalletHome())
.hasFinalState({
activation: { status: BonusActivationProgressEnum.ERROR },
allActive: {}
} as MockActivationState)
.run();
});
backendIntegrationTestCases.map(testCase =>
testCase.responses.map(response => {
return it(`${
testCase.displayName
}, startBonusActivation[${getDisplayNameBackendResponse(
response.startBonusActivationResponse
)}] with GetBonusActivationResponseById[${getDisplayNameBackendResponse(
response.getBonusActivationResponseById
)}]`, () =>
expectSagaFactory(
response,
testCase.expectedActions,
testCase.finalState
));
})
);
});

const expectSagaFactory = (
backendResponses: ActivationBackendResponse,
actionToVerify: ReadonlyArray<Action>,
finalState: MockActivationState
) => {
const startBonusActivation = jest.fn();
const getActivationById = jest.fn();
const baseSaga = expectSaga(
handleBonusActivationSaga,
bonusActivationSaga(startBonusActivation, getActivationById)
)
.provide([
[
select(navigationCurrentRouteSelector),
fromNullable(BONUSVACANZE_ROUTES.ACTIVATION.LOADING)
],
[
matchers.call.fn(startBonusActivation),
backendResponses.startBonusActivationResponse
],
[
matchers.call.fn(getActivationById),
backendResponses.getBonusActivationResponseById
]
])
.withReducer(activationReducer);
return (
actionToVerify
.reduce((acc, val) => acc.put(val), baseSaga)
// when the last event completeBonusVacanze is received, the navigation stack is popped
.dispatch(completeBonusVacanzeActivation())
.put(navigationHistoryPop(1))
.hasFinalState(finalState)
.run()
.then(results => {
expect(results.effects.select.length).toEqual(1);
// in this phase the put in the store is not tested, at the end I should have only one put action left
expect(results.effects.put.length).toEqual(1);
})
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { Either, left, right } from "fp-ts/lib/Either";
import { Errors } from "io-ts";
import { pot } from "italia-ts-commons";
import { ProblemJson } from "italia-ts-commons/lib/responses";
import { InstanceId } from "../../../../../../../../definitions/bonus_vacanze/InstanceId";
import { navigationHistoryPop } from "../../../../../../../store/actions/navigationHistory";
import { mockedBonus } from "../../../../../mock/mockData";
import {
navigateToBonusActivationCompleted,
navigateToBonusActiveDetailScreen,
navigateToBonusAlreadyExists,
navigateToEligibilityExpired
} from "../../../../../navigation/action";
import {
ActivationState,
BonusActivationProgressEnum
} from "../../../../reducers/activation";
import { AllActiveState } from "../../../../reducers/allActive";
import { IExpectedActions } from "../mockData";

const genericServiceUnavailable: Either<Errors, any> = right({
status: 500,
value: {
type: "https://example.com/problem/constraint-violation",
title: "string",
status: 500,
detail: "There was an error processing the request",
instance: "string"
} as ProblemJson
});

const genericDecodingFailure = left([
{ context: [], value: new Error("decoding failure") }
]);

// mock activation for /bonus/vacanze/activations POST

const startActivationRequestCreated: Either<Errors, any> = right({
status: 201,
value: { id: "bonus_id" } as InstanceId
});
const startActivationEligibilityExpired: Either<Errors, any> = right({
status: 403
});
const startActivationBonusAlreadyExists: Either<Errors, any> = right({
status: 409
});
const startActivationNoToken: Either<Errors, any> = right({ status: 401 });

// mock activation /bonus/vacanze/activations/{bonus_id} GET
const getActivationSuccess: Either<Errors, any> = right({
status: 200,
value: mockedBonus
});

const getActivationNoToken: Either<Errors, any> = right({
status: 401
});
const getActivationNoBonusFound: Either<Errors, any> = right({
status: 404
});

export type MockActivationState = {
activation: ActivationState;
allActive: AllActiveState;
};

export type ActivationBackendResponse = {
startBonusActivationResponse: Either<Errors, any>;
getBonusActivationResponseById: Either<Errors, any>;
};

interface MockBackendScenario extends IExpectedActions {
responses: ReadonlyArray<ActivationBackendResponse>;
finalState: MockActivationState;
}

// TODO: test polling timeout case
export const success: MockBackendScenario = {
displayName: "success",
responses: [
{
startBonusActivationResponse: startActivationRequestCreated,
getBonusActivationResponseById: getActivationSuccess
}
],
expectedActions: [
navigateToBonusActivationCompleted(),
navigateToBonusActiveDetailScreen({ bonus: mockedBonus }),
navigationHistoryPop(1)
],
finalState: {
activation: { status: BonusActivationProgressEnum.SUCCESS },
allActive: { [mockedBonus.id]: pot.some(mockedBonus) }
}
};

export const eligibilityExpired: MockBackendScenario = {
displayName: "eligibility expired",
responses: [
{
startBonusActivationResponse: startActivationEligibilityExpired,
getBonusActivationResponseById: right(undefined)
}
],
expectedActions: [navigateToEligibilityExpired(), navigationHistoryPop(1)],
finalState: {
activation: { status: BonusActivationProgressEnum.ELIGIBILITY_EXPIRED },
allActive: {}
}
};

export const bonusAlreadyExists: MockBackendScenario = {
displayName: "bonus already exists",
responses: [
{
startBonusActivationResponse: startActivationBonusAlreadyExists,
getBonusActivationResponseById: right(undefined)
}
],
expectedActions: [navigateToBonusAlreadyExists(), navigationHistoryPop(1)],
finalState: {
activation: { status: BonusActivationProgressEnum.EXISTS },
allActive: {}
}
};

export const error: MockBackendScenario = {
displayName: "error",
responses: [
{
startBonusActivationResponse: genericServiceUnavailable,
getBonusActivationResponseById: genericServiceUnavailable
},
{
startBonusActivationResponse: startActivationNoToken,
getBonusActivationResponseById: right(undefined)
},
{
startBonusActivationResponse: genericDecodingFailure,
getBonusActivationResponseById: right(undefined)
},
{
startBonusActivationResponse: startActivationRequestCreated,
getBonusActivationResponseById: genericServiceUnavailable
},
{
startBonusActivationResponse: startActivationRequestCreated,
getBonusActivationResponseById: getActivationNoToken
},
{
startBonusActivationResponse: startActivationRequestCreated,
getBonusActivationResponseById: getActivationNoBonusFound
},
{
startBonusActivationResponse: startActivationRequestCreated,
getBonusActivationResponseById: genericDecodingFailure
}
],
expectedActions: [],
finalState: {
activation: { status: BonusActivationProgressEnum.ERROR },
allActive: {}
}
};

export const backendIntegrationTestCases: ReadonlyArray<MockBackendScenario> = [
success,
eligibilityExpired,
bonusAlreadyExists,
error
];

test.skip("mockDataOnlyFile", () => undefined);
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import BONUSVACANZE_ROUTES from "../../../../navigation/routes";
import { bonusVacanzeActivation } from "../../../actions/bonusVacanze";
import { BonusActivationProgressEnum } from "../../../reducers/activation";

interface IExpectedActions {
export interface IExpectedActions {
displayName: string;
expectedActions: ReadonlyArray<Action>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export const bonusActivationSaga = (
`response status ${startBonusActivationProcedureResult.value.status}`
);
}
// decoding failure
throw Error(readableReport(startBonusActivationProcedureResult.value));
} catch (e) {
return bonusVacanzeActivation.failure(e);
}
Expand Down