CASH-2563: Bailout on 500 from GetPartsOrQuestions

This commit is contained in:
credelinghuys 2026-05-07 18:35:54 -04:00
parent dd291a980c
commit 75f0184314
8 changed files with 171 additions and 20 deletions

View file

@ -131,6 +131,8 @@ const storeActions = {
UPDATE_HAS_TRIGGERED_ERROR: "updateHasTriggeredError",
GET_VALID_IDEMPOTENCY_KEY: "getValidIdempotencyKey",
CORRECT_IDEMPOTENCY_KEY_EXPIRY: "correctIdempotencyKeyExpiry",
SAVE_BAILOUT_CODE: "saveBailoutCode",
};
export { storeActions };

View file

@ -114,6 +114,9 @@ const storeMutations = {
UPDATE_EXPERIMENTS: "updateExperiments",
UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry",
// BAILOUT MUTATIONS
UPDATE_BAILOUT_CODE: "updateBailoutCode",
// EXTERNAL_PARAMETER MUTATIONS
UPDATE_IS_EXTERNAL_PARAMETER: "updateIsExternalParameter",
UPDATE_EXTERNAL_PARAMETER_YEAR: "updateExternalParameterYear",

View file

@ -157,7 +157,7 @@ export default {
methods: {
getBailoutCodeFromStore() {
return bailoutCodes.PARTS_NOT_FOUND;
return store.getters.applicationUser.bailoutCode;
},
getFirstNameFromStore() {
return store.getters.order.customer.firstName;
@ -248,10 +248,11 @@ export default {
computed: {
getSubHeaderWidget() {
if (this.bailoutCode === bailoutCodes.PARTS_NOT_FOUND) {
return "PartsNotFoundSubHeaderWidget";
} else {
return "FunnelSubHeaderWidget";
switch (this.bailoutCode) {
case bailoutCodes.PARTS_NOT_FOUND:
return "PartsNotFoundSubHeaderWidget";
default:
return "FunnelSubHeaderWidget";
}
},
},

View file

@ -0,0 +1,13 @@
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
export default {
methods: {
navigateToBailoutPage(vm, bailoutCode) {
const self = vm ?? this;
self.dispatchStoreAction(self.storeActions.SAVE_BAILOUT_CODE, bailoutCode).then(() => {
self.$router.navigateWithoutSaving(navigationScenarios.BAILOUT, self.pageName);
});
},
},
};

View file

@ -0,0 +1,103 @@
import bailoutMixin from "@/mixins/bailout-mixin";
import { storeActions } from "@/constants/store-actions.js";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { bailoutCodes } from "@/constants/bailout-codes.js";
describe("bailout-mixin.js", () => {
test("navigateToBailoutPage: dispatches SAVE_BAILOUT_CODE action with bailout code", async () => {
// Arrange
const mockVm = createMockVm();
const bailoutCode = bailoutCodes.PARTS_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode);
// Assert
expect(mockVm.dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_BAILOUT_CODE,
bailoutCode
);
});
test("navigateToBailoutPage: navigates to bailout page after saving bailout code", async () => {
// Arrange
const mockVm = createMockVm();
const bailoutCode = bailoutCodes.PARTS_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode);
// Assert
expect(mockVm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.BAILOUT,
mockVm.pageName
);
});
test("navigateToBailoutPage: uses current context (this) when vm is not provided", async () => {
// Arrange
const mockRouter = {
navigateWithoutSaving: jest.fn().mockResolvedValue(undefined),
};
const mockThis = {
dispatchStoreAction: jest.fn().mockResolvedValue(undefined),
$router: mockRouter,
storeActions: storeActions,
pageName: "test-page",
};
const bailoutCode = bailoutCodes.PARTS_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage.call(mockThis, undefined, bailoutCode);
// Assert
expect(mockThis.dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_BAILOUT_CODE,
bailoutCode
);
});
test("navigateToBailoutPage: passes correct bailout code to store", async () => {
// Arrange
const mockVm = createMockVm();
const customBailoutCode = 999;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, customBailoutCode);
// Assert
expect(mockVm.dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_BAILOUT_CODE,
customBailoutCode
);
});
test("navigateToBailoutPage: calls navigateWithoutSaving with correct parameters", async () => {
// Arrange
const mockVm = createMockVm();
const mockPageName = "vehicle-damage";
mockVm.pageName = mockPageName;
const bailoutCode = bailoutCodes.PARTS_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode);
// Assert
expect(mockVm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.BAILOUT,
mockPageName
);
});
});
function createMockVm() {
return {
dispatchStoreAction: jest.fn().mockResolvedValue(undefined),
$router: {
navigateWithoutSaving: jest.fn().mockResolvedValue(undefined),
},
storeActions,
pageName: "test-page",
};
}

View file

@ -1,9 +1,11 @@
import { storeActions } from "@/constants/store-actions.js";
import store from "@/store";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import bailoutMixin from "@/mixins/bailout-mixin";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import { experimentSettings } from "@/constants/experiments";
import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js";
import { bailoutCodes } from "@/constants/bailout-codes";
export default {
computed: {
@ -34,6 +36,11 @@ export default {
const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS, {
pageNameToLog: pageName,
});
if (result.PartsNotFound) {
bailoutMixin.methods.navigateToBailoutPage(this, bailoutCodes.PARTS_NOT_FOUND);
}
const partsOrQuestions = result.data.partsOrQuestions;
vehicleQuestionsMixin.methods.navigateForward(partsOrQuestions, this);

View file

@ -274,6 +274,10 @@ const routingTable = function () {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.BAILOUT,
destinationPageData: routeData.BAILOUT,
},
],
},
{

View file

@ -216,6 +216,7 @@ const getDefaultState = () => {
affiliateCookies: [],
loggingOption: false,
hasAlreadyTriggeredError: false,
bailoutCode: null,
},
idempotencyKeyFields: {
referralCorrelationId: null,
@ -950,6 +951,9 @@ export const mutations = {
state.idempotencyKeyFields.totalInCents = totalInCents;
state.idempotencyKeyFields.expiryTime = expiryTime;
},
updateBailoutCode(state, bailoutCode) {
state.applicationUser.bailoutCode = bailoutCode;
},
};
// Export Getters
@ -1903,21 +1907,31 @@ export const actions = {
// create a new array to avoid mutating state
const glassArrayForPayload = convertGlassPieceNamingForApi(glassArray);
const response = await globalMethods.callHttpClient({
method: endpoints.GetPartsOrQuestions.method,
endpoint: endpoints.GetPartsOrQuestions.url,
payload: {
carId: carId,
glassPieces: glassArrayForPayload,
zip: zipCode,
vin: vin,
serviceType: serviceType,
referralSeqNumber: referralSeqNumber,
parentAccountNumber: parentAccountNumber,
},
logApiCall: true,
pageNameToLog: pageNameToLog,
});
const response = await globalMethods
.callHttpClient({
method: endpoints.GetPartsOrQuestions.method,
endpoint: endpoints.GetPartsOrQuestions.url,
payload: {
carId: carId,
glassPieces: glassArrayForPayload,
zip: zipCode,
vin: vin,
serviceType: serviceType,
referralSeqNumber: referralSeqNumber,
parentAccountNumber: parentAccountNumber,
},
logApiCall: true,
pageNameToLog: pageNameToLog,
})
.catch((error) => {
if (error.status == 500) {
return { PartsNotFound: true };
}
});
if (response.PartsNotFound) {
return response;
}
// Flatten location and name properties
response.data.partsOrQuestions = convertGlassPieceNamingFromApi(
@ -3820,6 +3834,10 @@ export const actions = {
context.commit(storeMutations.UPDATE_IDEMPOTENCY_KEY, newKey);
}
},
saveBailoutCode(context, bailoutCode) {
context.commit(storeMutations.UPDATE_BAILOUT_CODE, bailoutCode);
},
};
export default createStore({