"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