UNPKG

@ledgerhq/live-common

Version:
265 lines • 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const rxjs_1 = require("rxjs"); const core_1 = require("./core"); const errors_1 = require("@ledgerhq/errors"); const operators_1 = require("rxjs/operators"); const aTransportRef_1 = require("../mocks/aTransportRef"); // Needs to mock the timer from rxjs used in the retry mechanism jest.mock("rxjs", () => { const originalModule = jest.requireActual("rxjs"); return { ...originalModule, timer: jest.fn(() => { return (0, rxjs_1.of)(1); }), }; }); describe("sharedLogicTaskWrapper", () => { const task = jest.fn(); const wrappedTask = (0, core_1.sharedLogicTaskWrapper)(task); afterAll(() => { task.mockClear(); }); describe("When the task emits an non-error event", () => { it("should pass the event through", done => { task.mockReturnValue((0, rxjs_1.of)({ type: "data" })); wrappedTask().subscribe({ next: event => { try { expect(event).toEqual({ type: "data" }); done(); } catch (expectError) { done(expectError); } }, }); }); }); describe("When the task emits an error that is not handled by the shared logic", () => { it("should not retry the task and emits the error", done => { task.mockReturnValue((0, rxjs_1.throwError)(new Error("Unhandled error"))); wrappedTask().subscribe({ next: event => { try { expect(event).toEqual({ type: "error", error: new Error("Unhandled error"), retrying: false, }); done(); } catch (expectError) { done(expectError); } }, }); }); }); describe("When the task emits an error that is handled by the shared logic", () => { it("should retry infinitely and emits an error event until a correct event is emitted", done => { let counter = 0; task.mockReturnValue((0, rxjs_1.of)({ type: "data" }).pipe((0, operators_1.concatMap)(event => { if (counter < 3) { return (0, rxjs_1.throwError)(() => new errors_1.LockedDeviceError("Handled error")); } return (0, rxjs_1.of)(event); }))); wrappedTask().subscribe({ next: event => { try { if (counter < 3) { expect(event).toEqual({ type: "error", error: new errors_1.LockedDeviceError("Handled error"), retrying: true, }); } else { expect(event).toEqual({ type: "data" }); done(); } counter++; } catch (expectError) { done(expectError); } }, }); }); }); }); describe("retryOnErrorsCommandWrapper", () => { const command = jest.fn(); const disconnectedDeviceMaxRetries = 3; let transportRef; let wrappedCommand; beforeEach(async () => { transportRef = await (0, aTransportRef_1.aTransportRefBuilder)(); wrappedCommand = (0, core_1.retryOnErrorsCommandWrapper)({ command, allowedErrors: [ { maxRetries: disconnectedDeviceMaxRetries, errorClass: errors_1.DisconnectedDevice, }, { maxRetries: "infinite", errorClass: errors_1.LockedDeviceError, }, ], }); }); afterAll(() => { command.mockClear(); }); describe("When the command emits an non-error event", () => { it("should pass the event through", done => { command.mockReturnValue((0, rxjs_1.of)({ type: "data" })); wrappedCommand(transportRef).subscribe({ next: event => { try { expect(event).toEqual({ type: "data" }); done(); } catch (expectError) { done(expectError); } }, }); }); }); describe("When the command emits an error that is not set to be handled by the wrapper", () => { it("should not retry the command and throw the error", done => { command.mockReturnValue((0, rxjs_1.throwError)(() => new Error("Unhandled error"))); wrappedCommand(transportRef).subscribe({ error: error => { try { expect(error).toEqual(new Error("Unhandled error")); done(); } catch (expectError) { done(expectError); } }, }); }); }); describe("When the command throws an error that is set to be handled by the wrapper, and this error can be retried a limited number of times", () => { it("should retry the defined limited number of time and not emit an error event until a correct event is emitted", done => { let counter = 0; command.mockReturnValue((0, rxjs_1.of)({ type: "data" }).pipe((0, operators_1.concatMap)(event => { // Increments before the condition check below so it could keep incrementing after reaching disconnectedDeviceMaxRetries // to make sure the event is received the first time it is emitted and no other retry occurred after counter++; // Throws an error until before the limit is reached if (counter < disconnectedDeviceMaxRetries) { return (0, rxjs_1.throwError)(() => new errors_1.DisconnectedDevice(`Handled error max ${disconnectedDeviceMaxRetries}`)); } return (0, rxjs_1.of)(event); }))); wrappedCommand(transportRef).subscribe({ next: event => { try { // It reaches disconnectedDeviceMaxRetries because of our condition inside the mocked task // but it could be anything <= disconnectedDeviceMaxRetries. expect(counter).toBe(disconnectedDeviceMaxRetries); // It should not receive error event, the retry is silent, and only the data event should be received expect(event).toEqual({ type: "data" }); done(); } catch (expectError) { done(expectError); } }, }); }); it("should retry a limited number of time and throw the error if it is not resolved", done => { let counter = 0; command.mockReturnValue((0, rxjs_1.of)({ type: "data" }).pipe((0, operators_1.concatMap)(_event => { counter++; // Always throws an error, exceeding the set max retry return (0, rxjs_1.throwError)(() => new errors_1.DisconnectedDevice(`Handled error max ${disconnectedDeviceMaxRetries}`)); }))); wrappedCommand(transportRef).subscribe({ error: error => { try { expect(counter).toBe(disconnectedDeviceMaxRetries + 1); expect(error).toEqual(new errors_1.DisconnectedDevice(`Handled error max ${disconnectedDeviceMaxRetries}`)); done(); } catch (expectError) { done(expectError); } }, }); }); describe("and several type of errors are thrown", () => { it("should retry until one type of error is retried the maximum number of time in a row", done => { let counter = 0; command.mockReturnValue((0, rxjs_1.of)({ type: "data" }).pipe((0, operators_1.concatMap)(_event => { counter++; // Throws an error until just before the limit is reached if (counter < disconnectedDeviceMaxRetries) { return (0, rxjs_1.throwError)(() => new errors_1.DisconnectedDevice(`Handled error max ${disconnectedDeviceMaxRetries}`)); } // Then throws a different handled error else if (counter < disconnectedDeviceMaxRetries + 1) { return (0, rxjs_1.throwError)(() => new errors_1.LockedDeviceError("Handled error")); } // Finally throws again the first limited handled error // It should retry again until disconnctedDeviceMaxRetries is again reached // Which is counter == disconnectedDeviceMaxRetries * 2 + 1 else { return (0, rxjs_1.throwError)(() => new errors_1.DisconnectedDevice(`Handled error max ${disconnectedDeviceMaxRetries}`)); } }))); const expectedCounterAtDisconnectedDeviceError = disconnectedDeviceMaxRetries * 2 + 1; wrappedCommand(transportRef).subscribe({ error: error => { try { expect(counter).toBe(expectedCounterAtDisconnectedDeviceError); expect(error).toEqual(new errors_1.DisconnectedDevice(`Handled error max ${disconnectedDeviceMaxRetries}`)); done(); } catch (expectError) { done(expectError); } }, }); }); }); }); describe("When the command throws an error that is set to be handled by the wrapper, and this error can be retried an infinite number of times", () => { it("should retry infinitely, without throwing an error, until a correct event is emitted", done => { let counter = 0; // The default retry time is 500ms: testing a total time higher than the 5000ms that triggers a Jest timeout // as the time should be mocked/faked const randomNumberOfRetries = Math.floor(Math.random() * 5) + 11; command.mockReturnValue((0, rxjs_1.of)({ type: "data" }).pipe((0, operators_1.concatMap)(event => { counter++; // Throws an error until a random number of times if (counter < randomNumberOfRetries) { return (0, rxjs_1.throwError)(() => new errors_1.LockedDeviceError(`Handled infinite retries error that should be thrown ${randomNumberOfRetries} times`)); } return (0, rxjs_1.of)(event); }))); wrappedCommand(transportRef).subscribe({ next: event => { try { // No error or event should have been emitted before the correct event expect(counter).toBe(randomNumberOfRetries); expect(event).toEqual({ type: "data" }); done(); } catch (expectError) { done(expectError); } }, error: error => done(error), }); }); }); }); //# sourceMappingURL=core.test.js.map