UNPKG

@ledgerhq/live-common

Version:
299 lines • 14.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const hw_transport_1 = __importDefault(require("@ledgerhq/hw-transport")); const getDeviceInfo_1 = __importDefault(require("./getDeviceInfo")); const genuineCheck_1 = __importDefault(require("./genuineCheck")); const getGenuineCheckFromDeviceId_1 = require("./getGenuineCheckFromDeviceId"); const errors_1 = require("@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(rxjs_1.timer); // Only mocks withDevice jest.mock("./deviceAccess", () => { const originalModule = jest.requireActual("./deviceAccess"); return { ...originalModule, withDevice: jest.fn().mockReturnValue(job => { return (0, rxjs_1.from)(job(new hw_transport_1.default())); }), }; }); jest.mock("./getDeviceInfo"); jest.mock("./genuineCheck"); const mockedGetDeviceInfo = jest.mocked(getDeviceInfo_1.default); const mockedGenuineCheck = jest.mocked(genuineCheck_1.default); 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((0, rxjs_1.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((0, rxjs_1.firstValueFrom)((0, rxjs_1.of)(aDeviceInfo).pipe((0, operators_1.delay)(1001)))); mockedGenuineCheck.mockReturnValue((0, rxjs_1.of)({ type: "device-permission-requested", })); let step = 1; (0, getGenuineCheckFromDeviceId_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 rxjs_1.Observable(o => { if (count < 1) { count++; o.error(new errors_1.LockedDeviceError("Locked device")); } else { o.next(aDeviceInfo); } }); }); mockedGenuineCheck.mockReturnValue((0, rxjs_1.of)({ type: "device-permission-requested", })); let step = 1; (0, getGenuineCheckFromDeviceId_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 rxjs_1.Observable(subscriber => { setTimeout(() => { subscriber.next(1); }, dueTime); }); } else { return (0, rxjs_1.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 rxjs_1.Observable(o => { if (count < 1) { count++; o.error(new errors_1.LockedDeviceError("Locked device")); } else { o.next(aDeviceInfo); } }); }); mockedGenuineCheck.mockReturnValue((0, rxjs_1.of)({ type: "device-permission-requested", })); let step = 1; const subscriber = (0, getGenuineCheckFromDeviceId_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: 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 rxjs_1.Observable(o => { if (count < 1) { count++; o.error(new errors_1.LockedDeviceError("Locked device")); } else { o.next({ type: "device-permission-requested", }); } }); }); let step = 1; (0, getGenuineCheckFromDeviceId_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