This adds a circuit breaker for uncaught failures in beforeEach. The first failure still goes through the normal ERROR/RESTART recovery path; a second consecutive failure redirects to the static error page instead of looping. - New before-each-error-recovery module tracks recovery attempts in sessionStorage (with an in-memory fallback). - ERROR/RESTART routes skip normal guard logic so the counter can accumulate across bounce-backs. - The counter clears only after a fully successful navigation. - redirectToStaticErrorPage centralizes hard-error bailout: logs, clears poisoned sessionStorage keys (submittedState, heritage redirect count, recovery count, externalParameterState), then navigates to /fmg/static/error. - The static error page RESTART flow clears the same sessionStorage keys so bad state does not re-enter the funnel. - Session storage key strings are centralized in session-storage.js. - Unit tests cover recovery counting, bailout clearing, and navigation fallbacks. Expected impact: Stops repeated ERROR → RESTART → ERROR cycles (including corrupt submittedState JSON.parse failures and recurring automation errors in us-east-1/us-east-2) by bailing out to the static error page after one failed recovery attempt.
80 lines
2.8 KiB
JavaScript
80 lines
2.8 KiB
JavaScript
import {
|
|
clearBeforeEachErrorRecoveryCount,
|
|
getBeforeEachErrorRecoveryCount,
|
|
hasBeforeEachErrorRecoveryBeenAttempted,
|
|
incrementBeforeEachErrorRecoveryCount,
|
|
resetBeforeEachErrorRecoveryStateForTests,
|
|
} from "./before-each-error-recovery";
|
|
import { sessionStorageKeyConstants } from "@/constants/session-storage";
|
|
|
|
describe("router/methods/before-each-error-recovery", () => {
|
|
const recoveryKey = sessionStorageKeyConstants.BEFORE_EACH_ERROR_RECOVERY_COUNT;
|
|
let originalSessionStorage;
|
|
|
|
beforeEach(() => {
|
|
resetBeforeEachErrorRecoveryStateForTests();
|
|
originalSessionStorage = window.sessionStorage;
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(window, "sessionStorage", {
|
|
configurable: true,
|
|
value: originalSessionStorage,
|
|
});
|
|
resetBeforeEachErrorRecoveryStateForTests();
|
|
});
|
|
|
|
it("reads and writes recovery count via sessionStorage when available", () => {
|
|
incrementBeforeEachErrorRecoveryCount();
|
|
|
|
expect(window.sessionStorage.getItem(recoveryKey)).toBe("1");
|
|
expect(getBeforeEachErrorRecoveryCount()).toBe(1);
|
|
expect(hasBeforeEachErrorRecoveryBeenAttempted()).toBe(true);
|
|
});
|
|
|
|
it("clears sessionStorage and in-memory count", () => {
|
|
incrementBeforeEachErrorRecoveryCount();
|
|
clearBeforeEachErrorRecoveryCount();
|
|
|
|
expect(window.sessionStorage.getItem(recoveryKey)).toBeNull();
|
|
expect(getBeforeEachErrorRecoveryCount()).toBe(0);
|
|
expect(hasBeforeEachErrorRecoveryBeenAttempted()).toBe(false);
|
|
});
|
|
|
|
it("uses in-memory count when sessionStorage getItem throws", () => {
|
|
Object.defineProperty(window, "sessionStorage", {
|
|
configurable: true,
|
|
value: {
|
|
getItem: () => {
|
|
throw new Error("sessionStorage blocked");
|
|
},
|
|
setItem: jest.fn(),
|
|
removeItem: jest.fn(),
|
|
},
|
|
});
|
|
|
|
incrementBeforeEachErrorRecoveryCount();
|
|
incrementBeforeEachErrorRecoveryCount();
|
|
|
|
expect(hasBeforeEachErrorRecoveryBeenAttempted()).toBe(true);
|
|
expect(getBeforeEachErrorRecoveryCount()).toBe(2);
|
|
});
|
|
|
|
it("uses in-memory count when sessionStorage setItem throws", () => {
|
|
Object.defineProperty(window, "sessionStorage", {
|
|
configurable: true,
|
|
value: {
|
|
getItem: () => null,
|
|
setItem: () => {
|
|
throw new Error("sessionStorage blocked");
|
|
},
|
|
removeItem: jest.fn(),
|
|
},
|
|
});
|
|
|
|
incrementBeforeEachErrorRecoveryCount();
|
|
|
|
expect(hasBeforeEachErrorRecoveryBeenAttempted()).toBe(true);
|
|
expect(getBeforeEachErrorRecoveryCount()).toBe(1);
|
|
});
|
|
});
|