import { firstValueFrom, from, Observable, of, timer } from "rxjs";
import { delay } from "rxjs/operators";
import Transport from "@ledgerhq/hw-transport";
import getDeviceInfo from "./getDeviceInfo";
import genuineCheck from "./genuineCheck";
import { getGenuineCheckFromDeviceId, } from "./getGenuineCheckFromDeviceId";
import { LockedDeviceError } from "@ledgerhq/errors";
jest.useFakeTimers();
// Needs to mock the timer from rxjs used in retryWhileErrors
jest.mock("rxjs", () => {
    const originalModule = jest.requireActual("rxjs");
    return {
        ...originalModule,
        timer: jest.fn(),
    };
});
const mockedTimer = jest.mocked(timer);
// Only mocks withDevice
jest.mock("./deviceAccess", () => {
    const originalModule = jest.requireActual("./deviceAccess");
    return {
        ...originalModule,
        withDevice: jest.fn().mockReturnValue(job => {
            return from(job(new Transport()));
        }),
    };
});
jest.mock("./getDeviceInfo");
jest.mock("./genuineCheck");
const mockedGetDeviceInfo = jest.mocked(getDeviceInfo);
const mockedGenuineCheck = jest.mocked(genuineCheck);
const aDeviceInfo = {
    mcuVersion: "A_MCU_VERSION",
    version: "A_VERSION",
    majMin: "A_MAJ_MIN",
    targetId: "0.0",
    isBootloader: true,
    isOSU: true,
    providerName: undefined,
    managerAllowed: false,
    pinValidated: true,
    seFlags: Buffer.alloc(0),
};
describe("getGenuineCheckFromDeviceId", () => {
    beforeEach(() => {
        // Mocked timer: directly pushes and complete
        // @ts-expect-error the mocked function reflect an incorrect signature
        mockedTimer.mockReturnValue(of(1));
    });
    afterEach(() => {
        mockedGetDeviceInfo.mockClear();
        mockedGenuineCheck.mockClear();
        mockedTimer.mockClear();
        jest.clearAllTimers();
    });
    describe("When the device is locked BEFORE doing a genuine check", () => {
        describe("And the mechanism to know that a device is locked is timing out", () => {
            it("should notify the function consumer of the need to unlock the device, and once done, continue the genuine check flow", done => {
                // Delays the device info response
                mockedGetDeviceInfo.mockReturnValue(firstValueFrom(of(aDeviceInfo).pipe(delay(1001))));
                mockedGenuineCheck.mockReturnValue(of({
                    type: "device-permission-requested",
                }));
                let step = 1;
                getGenuineCheckFromDeviceId({
                    deviceId: "A_DEVICE_ID",
                    lockedDeviceTimeoutMs: 1000,
                }).subscribe({
                    next: ({ socketEvent, lockedDevice }) => {
                        try {
                            switch (step) {
                                case 1:
                                    expect(socketEvent).toBeNull();
                                    expect(lockedDevice).toBe(true);
                                    break;
                                case 2:
                                    expect(socketEvent).toBeNull();
                                    expect(lockedDevice).toBe(false);
                                    break;
                                case 3:
                                    expect(socketEvent).toEqual({
                                        type: "device-permission-requested",
                                    });
                                    expect(lockedDevice).toBe(false);
                                    done();
                                    break;
                            }
                        }
                        catch (expectError) {
                            done(expectError);
                        }
                        jest.advanceTimersByTime(1);
                        step += 1;
                    },
                });
                // Unresponsive timeout is triggered
                jest.advanceTimersByTime(1000);
            });
        });
        describe("And the mechanism to know that a device is locked is a 0x5515 locked-device response", () => {
            it("should notify the function consumer of the need to unlock the device, and once done, continue the genuine check flow", done => {
                let count = 0;
                // Could not simply mockedRejectValueOnce followed by a mockedResolveValueOnce.
                // Needed to transform getDeviceInfo into an Observable.
                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                // @ts-ignore returning an Observable and not a Promise.
                mockedGetDeviceInfo.mockImplementation(() => {
                    return new Observable(o => {
                        if (count < 1) {
                            count++;
                            o.error(new LockedDeviceError("Locked device"));
                        }
                        else {
                            o.next(aDeviceInfo);
                        }
                    });
                });
                mockedGenuineCheck.mockReturnValue(of({
                    type: "device-permission-requested",
                }));
                let step = 1;
                getGenuineCheckFromDeviceId({
                    deviceId: "A_DEVICE_ID",
                    lockedDeviceTimeoutMs: 1000,
                }).subscribe({
                    next: ({ socketEvent, lockedDevice }) => {
                        try {
                            switch (step) {
                                case 1:
                                    expect(socketEvent).toBeNull();
                                    expect(lockedDevice).toBe(true);
                                    // No need to advance the timer, as the retry timer is mocked to return directly, without a timeout
                                    break;
                                // A retry happened, this time with an unlocked device
                                case 2:
                                    expect(socketEvent).toBeNull();
                                    expect(lockedDevice).toBe(false);
                                    break;
                                case 3:
                                    expect(socketEvent).toEqual({
                                        type: "device-permission-requested",
                                    });
                                    expect(lockedDevice).toBe(false);
                                    done();
                                    break;
                            }
                        }
                        catch (expectError) {
                            done(expectError);
                        }
                        step += 1;
                    },
                });
            });
        });
        describe("And the genuine check consumer unsubscribed before unlocking the device", () => {
            beforeEach(() => {
                // Mocked timer: pushes and complete after a timeout
                // Needed so the retry is not triggered before unsubscribing during our test
                // @ts-expect-error the mocked function expects an Observable<0>
                // while we give it an Observable<1>, timer has multiple signatures and I can't figure
                // out why it doesn't understand the correct one
                mockedTimer.mockImplementation((dueTime) => {
                    if (typeof dueTime === "number") {
                        return new Observable(subscriber => {
                            setTimeout(() => {
                                subscriber.next(1);
                            }, dueTime);
                        });
                    }
                    else {
                        return of(1);
                    }
                });
            });
            it("should stop the genuine check flow, to avoid sending an allow-secure-channel request to the device after unlocking it", done => {
                let count = 0;
                // Could not simply mockedRejectValueOnce followed by a mockedResolveValueOnce.
                // Needed to transform getDeviceInfo into an Observable.
                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                // @ts-ignore returning an Observable and not a Promise.
                mockedGetDeviceInfo.mockImplementation(() => {
                    return new Observable(o => {
                        if (count < 1) {
                            count++;
                            o.error(new LockedDeviceError("Locked device"));
                        }
                        else {
                            o.next(aDeviceInfo);
                        }
                    });
                });
                mockedGenuineCheck.mockReturnValue(of({
                    type: "device-permission-requested",
                }));
                let step = 1;
                const subscriber = getGenuineCheckFromDeviceId({
                    deviceId: "A_DEVICE_ID",
                    lockedDeviceTimeoutMs: 1000,
                }).subscribe({
                    next: ({ socketEvent, lockedDevice }) => {
                        try {
                            switch (step) {
                                case 1:
                                    expect(socketEvent).toBeNull();
                                    expect(lockedDevice).toBe(true);
                                    break;
                                case 2:
                                    done("A retry happened, this should never be reached here");
                                    break;
                            }
                        }
                        catch (expectError) {
                            done(expectError);
                        }
                        step += 1;
                    },
                });
                // Step 1: Starts the genuine check, and the device is locked
                jest.advanceTimersByTime(1);
                // Tries to stop the genuine check after step 1
                expect(step).toEqual(2);
                subscriber.unsubscribe();
                // Step 2: Triggers any existing retry (if stopped correctly, there should be no retry)
                jest.runOnlyPendingTimers();
                // Checks if it was stopped correctly
                expect(mockedGetDeviceInfo).toBeCalledTimes(1);
                expect(mockedGenuineCheck).toBeCalledTimes(0);
                done();
            });
        });
    });
    describe("When the device is locked DURING the genuine check", () => {
        describe("And the mechanism to know that a device is locked is a 0x5515 locked-device response", () => {
            it("should notify the function consumer of the need to unlock the device, and once done, continue the genuine check flow", done => {
                mockedGetDeviceInfo.mockResolvedValue(aDeviceInfo);
                let count = 0;
                mockedGenuineCheck.mockImplementation(() => {
                    return new Observable(o => {
                        if (count < 1) {
                            count++;
                            o.error(new LockedDeviceError("Locked device"));
                        }
                        else {
                            o.next({
                                type: "device-permission-requested",
                            });
                        }
                    });
                });
                let step = 1;
                getGenuineCheckFromDeviceId({
                    deviceId: "A_DEVICE_ID",
                    lockedDeviceTimeoutMs: 1000,
                }).subscribe({
                    next: ({ socketEvent, lockedDevice }) => {
                        try {
                            switch (step) {
                                // No locked device at first
                                case 1:
                                    expect(lockedDevice).toBe(false);
                                    expect(socketEvent).toBeNull();
                                    break;
                                // The locked device happens during the genuine check
                                case 2:
                                    expect(socketEvent).toBeNull();
                                    expect(lockedDevice).toBe(true);
                                    // No need to advance the timer, as the retry timer is mocked to return directly, without a timeout
                                    break;
                                // A retry happened, this time with an unlocked device
                                case 3:
                                    expect(socketEvent).toBeNull();
                                    expect(lockedDevice).toBe(false);
                                    break;
                                case 4:
                                    expect(socketEvent).toEqual({
                                        type: "device-permission-requested",
                                    });
                                    expect(lockedDevice).toBe(false);
                                    done();
                                    break;
                            }
                        }
                        catch (expectError) {
                            done(expectError);
                        }
                        step += 1;
                    },
                });
            });
        });
    });
});
//# sourceMappingURL=getGenuineCheckFromDeviceId.test.js.map