UNPKG

@ledgerhq/live-common

Version:
317 lines • 14.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const getOnboardingStatePolling_1 = require("./getOnboardingStatePolling"); const rxjs_1 = require("rxjs"); const rxjsOperators = __importStar(require("rxjs/operators")); const devices_1 = require("@ledgerhq/devices"); const hw_transport_1 = __importDefault(require("@ledgerhq/hw-transport")); const errors_1 = require("@ledgerhq/errors"); const deviceAccess_1 = require("./deviceAccess"); const getVersionUseCase_1 = require("../device/use-cases/getVersionUseCase"); const extractOnboardingState_1 = require("./extractOnboardingState"); const types_live_1 = require("@ledgerhq/types-live"); const device_management_kit_1 = require("@ledgerhq/device-management-kit"); jest.mock("./deviceAccess"); jest.mock("../device/use-cases/getVersionUseCase"); jest.mock("./extractOnboardingState"); jest.mock("@ledgerhq/hw-transport"); jest.useFakeTimers(); const aDevice = { deviceId: "DEVICE_ID_A", deviceName: "DEVICE_NAME_A", modelId: devices_1.DeviceModelId.stax, wired: false, }; // As extractOnboardingState is mocked, the firmwareInfo // returned by getVersion does not matter const aFirmwareInfo = { isBootloader: false, rawVersion: "", targetId: 0, mcuVersion: "", flags: Buffer.from([]), }; const pollingPeriodMs = 1000; const mockedGetVersion = jest.mocked(getVersionUseCase_1.getVersion); const mockedWithDevice = jest.mocked(deviceAccess_1.withDevice); mockedWithDevice.mockReturnValue(job => (0, rxjs_1.from)(job(new hw_transport_1.default()))); const mockedExtractOnboardingState = jest.mocked(extractOnboardingState_1.extractOnboardingState); describe("getOnboardingStatePolling", () => { let anOnboardingState; let onboardingStatePollingSubscription; beforeEach(() => { anOnboardingState = { isOnboarded: false, isInRecoveryMode: false, seedPhraseType: types_live_1.SeedPhraseType.TwentyFour, currentSeedWordIndex: 0, currentOnboardingStep: extractOnboardingState_1.OnboardingStep.NewDevice, charonSupported: false, charonStatus: null, }; }); afterEach(() => { mockedGetVersion.mockClear(); mockedExtractOnboardingState.mockClear(); jest.clearAllTimers(); onboardingStatePollingSubscription?.unsubscribe(); }); describe("When a communication error occurs while fetching the device state", () => { describe("and when the error is allowed and thrown before the defined timeout", () => { it("should update the onboarding state to null and keep track of the allowed error", done => { mockedGetVersion.mockRejectedValue(new errors_1.DisconnectedDevice("An allowed error")); mockedExtractOnboardingState.mockReturnValue(anOnboardingState); const device = aDevice; (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, }).subscribe({ next: value => { try { expect(value.onboardingState).toBeNull(); expect(value.allowedError).toBeInstanceOf(errors_1.DisconnectedDevice); expect(value.lockedDevice).toBe(false); done(); } catch (expectError) { done(expectError); } }, }); // The timeout is equal to pollingPeriodMs by default jest.advanceTimersByTime(pollingPeriodMs - 1); }); it("should update the onboarding state to null and keep track of the allowed DMK error", done => { mockedGetVersion.mockRejectedValue(new device_management_kit_1.DeviceDisconnectedWhileSendingError("An allowed error")); mockedExtractOnboardingState.mockReturnValue(anOnboardingState); const device = aDevice; (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, }).subscribe({ next: value => { try { expect(value.onboardingState).toBeNull(); expect(value.allowedError).toBeInstanceOf(device_management_kit_1.DeviceDisconnectedWhileSendingError); expect(value.lockedDevice).toBe(false); done(); } catch (expectError) { done(expectError); } }, }); // The timeout is equal to pollingPeriodMs by default jest.advanceTimersByTime(pollingPeriodMs - 1); }); }); describe("and when the error is due to a locked device", () => { it("should update the lockedDevice, update the onboarding state to null and keep track of the allowed error", done => { mockedGetVersion.mockRejectedValue(new errors_1.LockedDeviceError()); mockedExtractOnboardingState.mockReturnValue(anOnboardingState); const device = aDevice; (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, }).subscribe({ next: value => { try { expect(value.onboardingState).toBeNull(); expect(value.allowedError).toBeInstanceOf(errors_1.LockedDeviceError); expect(value.lockedDevice).toBe(true); done(); } catch (expectError) { done(expectError); } }, }); // The timeout is equal to pollingPeriodMs by default jest.advanceTimersByTime(pollingPeriodMs - 1); }); }); describe("and when a timeout occurred before the error (because the response from the device took too long)", () => { it("should update the allowed error value to notify the consumer", done => { const safeGuardTimeoutMs = pollingPeriodMs + 500; mockedGetVersion.mockResolvedValue(aFirmwareInfo); mockedExtractOnboardingState.mockReturnValue(anOnboardingState); const device = aDevice; (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, safeGuardTimeoutMs, }).subscribe({ next: value => { try { expect(value.onboardingState).toBeNull(); expect(value.allowedError).toBeInstanceOf(rxjs_1.TimeoutError); expect(value.lockedDevice).toBe(false); done(); } catch (expectError) { done(expectError); } }, }); // Waits more than the timeout jest.advanceTimersByTime(safeGuardTimeoutMs + 1); }); }); describe("and when the error is fatal and thrown before the defined timeout", () => { it("should notify the consumer that a unallowed error occurred", done => { mockedGetVersion.mockRejectedValue(new Error("Unknown error")); const device = aDevice; (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, }).subscribe({ error: error => { try { expect(error).toBeInstanceOf(Error); expect(error?.message).toBe("Unknown error"); done(); } catch (expectError) { done(expectError); } }, }); jest.advanceTimersByTime(pollingPeriodMs - 1); }); }); }); describe("When the fetched device state is incorrect", () => { it("should return a null onboarding state, and keep track of the extract error", done => { mockedGetVersion.mockResolvedValue(aFirmwareInfo); mockedExtractOnboardingState.mockImplementation(() => { throw new errors_1.DeviceExtractOnboardingStateError("Some incorrect device info"); }); const device = aDevice; onboardingStatePollingSubscription = (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, }).subscribe({ next: value => { try { expect(value.onboardingState).toBeNull(); expect(value.allowedError).toBeInstanceOf(errors_1.DeviceExtractOnboardingStateError); expect(value.lockedDevice).toBe(false); done(); } catch (expectError) { done(expectError); } }, }); jest.advanceTimersByTime(pollingPeriodMs - 1); }); }); describe("When polling returns a correct device state", () => { it("should return a correct onboarding state", done => { mockedGetVersion.mockResolvedValue(aFirmwareInfo); mockedExtractOnboardingState.mockReturnValue(anOnboardingState); const device = aDevice; onboardingStatePollingSubscription = (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, }).subscribe({ next: value => { try { expect(value.allowedError).toBeNull(); expect(value.onboardingState).toEqual(anOnboardingState); expect(value.lockedDevice).toBe(false); done(); } catch (expectError) { done(expectError); } }, error: error => { done(error); }, }); jest.advanceTimersByTime(pollingPeriodMs - 1); }); it("should poll a new onboarding state after the defined period of time", done => { mockedGetVersion.mockResolvedValue(aFirmwareInfo); mockedExtractOnboardingState.mockReturnValue(anOnboardingState); const device = aDevice; // Did not manage to test that the polling is repeated by using jest's fake timer // and advanceTimersByTime method or equivalent. // Hacky test: spy on the repeatWhen operator to see if it has been called. const spiedRepeat = jest.spyOn(rxjsOperators, "repeat"); onboardingStatePollingSubscription = (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, safeGuardTimeoutMs: pollingPeriodMs * 10, }).subscribe({ next: value => { try { expect(value.onboardingState).toEqual(anOnboardingState); expect(value.allowedError).toBeNull(); expect(value.lockedDevice).toBe(false); expect(spiedRepeat).toHaveBeenCalledTimes(1); done(); } catch (expectError) { done(expectError); } }, error: error => { done(error); }, }); }); }); describe("When the device is in bootloader mode", () => { it("should throw an error so it is considered a fatal error", done => { mockedGetVersion.mockResolvedValue({ ...aFirmwareInfo, isBootloader: true }); const device = aDevice; onboardingStatePollingSubscription = (0, getOnboardingStatePolling_1.getOnboardingStatePolling)({ deviceId: device.deviceId, pollingPeriodMs, }).subscribe({ next: value => { done(`It should have thrown an error. Received value: ${JSON.stringify(value)}`); }, error: error => { try { expect(error).toBeInstanceOf(errors_1.UnexpectedBootloader); done(); } catch (expectError) { done(expectError); } }, }); jest.advanceTimersByTime(pollingPeriodMs - 1); }); }); }); //# sourceMappingURL=getOnboardingStatePolling.test.js.map