@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
265 lines • 14.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const rxjs_1 = require("rxjs");
const errors_1 = require("@ledgerhq/errors");
const getDeviceInfo_1 = require("../tasks/getDeviceInfo");
const getLatestFirmware_1 = require("../tasks/getLatestFirmware");
const aDeviceInfo_1 = require("../../mock/fixtures/aDeviceInfo");
const aLatestFirmwareContext_1 = require("../../mock/fixtures/aLatestFirmwareContext");
const getLatestAvailableFirmware_1 = require("./getLatestAvailableFirmware");
const core_1 = require("../tasks/core");
// Needed for rxjs timeout
jest.useFakeTimers();
// Needs to mock the timer from rxjs used in the task retry mechanism (`retry` with `timer`)
jest.mock("rxjs", () => {
const originalModule = jest.requireActual("rxjs");
return {
...originalModule,
timer: jest.fn(() => {
return (0, rxjs_1.of)(1);
}),
};
});
jest.mock("../tasks/getDeviceInfo");
// Going to test also the retry mechanism of the tasks associated to the action
const mockedInternalGetDeviceInfoTask = jest.mocked(getDeviceInfo_1.internalGetDeviceInfoTask);
const mockedGetDeviceInfoTask = jest.mocked(getDeviceInfo_1.getDeviceInfoTask);
// `sharedLogicTaskWrapper` contains the retry logic, and we want to test it.
// If we only mock `internalGetDeviceInfoTask`, `getDeviceInfoTask` will still be defined with the original
// `internalGetDeviceInfoTask` and not the mocked version. So we need to redefine it.
mockedGetDeviceInfoTask.mockImplementation((0, core_1.sharedLogicTaskWrapper)(getDeviceInfo_1.internalGetDeviceInfoTask));
jest.mock("../tasks/getLatestFirmware");
const mockedGetLatestFirmwareTask = jest.mocked(getLatestFirmware_1.getLatestFirmwareTask);
describe("getLatestAvailableFirmwareAction", () => {
let aDeviceInfo;
let aLatestFirmwareContext;
beforeEach(() => {
aDeviceInfo = (0, aDeviceInfo_1.aDeviceInfoBuilder)();
aLatestFirmwareContext = (0, aLatestFirmwareContext_1.aLatestFirmwareContextBuilder)();
// mockedTimer.mockReturnValue(of(1));
});
afterEach(() => {
mockedInternalGetDeviceInfoTask.mockClear();
mockedGetLatestFirmwareTask.mockClear();
// mockedTimer.mockClear();
jest.clearAllTimers();
});
describe("The device is in a correct state", () => {
it("should return the latest available firmware for the device if there is one", done => {
// Happy path
mockedInternalGetDeviceInfoTask.mockReturnValue((0, rxjs_1.of)({ type: "data", deviceInfo: aDeviceInfo }));
mockedGetLatestFirmwareTask.mockReturnValue((0, rxjs_1.of)({ type: "data", firmwareUpdateContext: aLatestFirmwareContext }));
let step = 1;
(0, getLatestAvailableFirmware_1.getLatestAvailableFirmwareAction)({
deviceId: "A_DEVICE_ID",
}).subscribe({
next: ({ firmwareUpdateContext, deviceInfo, lockedDevice, status, }) => {
try {
switch (step) {
case 1:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toBeNull();
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
break;
case 2:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
break;
case 3:
expect(firmwareUpdateContext).toEqual(aLatestFirmwareContext);
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("available-firmware");
done();
break;
}
}
catch (expectError) {
done(expectError);
}
step += 1;
},
});
});
it("should return no latest available firmware information if there is none for the device", done => {
// Happy path
mockedInternalGetDeviceInfoTask.mockReturnValue((0, rxjs_1.of)({ type: "data", deviceInfo: aDeviceInfo }));
mockedGetLatestFirmwareTask.mockReturnValue((0, rxjs_1.of)({
type: "taskError",
error: "FirmwareUpToDate",
}));
let step = 1;
(0, getLatestAvailableFirmware_1.getLatestAvailableFirmwareAction)({
deviceId: "A_DEVICE_ID",
}).subscribe({
next: ({ firmwareUpdateContext, deviceInfo, lockedDevice, status, }) => {
try {
switch (step) {
case 1:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toBeNull();
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
break;
case 2:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
break;
case 3:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("no-available-firmware");
done();
break;
}
}
catch (expectError) {
done(expectError);
}
step += 1;
},
});
});
});
describe("When the device is locked", () => {
it("should notify the function consumer of the need to unlock the device, and once done, continue the get latest available firmware flow", done => {
let count = 0;
// Returns a LockedDeviceError first, then the device info pretending the device has been unlocked
mockedInternalGetDeviceInfoTask.mockReturnValue(new rxjs_1.Observable(o => {
if (count < 1) {
count++;
// Mocks the internal task, some shared error are thrown (like `LockedDeviceError`)
// and caught by the `sharedLogicTaskWrapper`
o.error(new errors_1.LockedDeviceError());
}
else {
o.next({ type: "data", deviceInfo: aDeviceInfo });
}
}));
mockedGetLatestFirmwareTask.mockReturnValue((0, rxjs_1.of)({ type: "data", firmwareUpdateContext: aLatestFirmwareContext }));
let step = 1;
(0, getLatestAvailableFirmware_1.getLatestAvailableFirmwareAction)({
deviceId: "A_DEVICE_ID",
}).subscribe({
next: ({ firmwareUpdateContext, deviceInfo, lockedDevice, status, error, }) => {
try {
switch (step) {
case 1:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toBeNull();
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
expect(error).toBeNull();
break;
case 2:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toBeNull();
expect(lockedDevice).toBe(true);
// The actions is retrying its inner tasks on a locked device error
expect(status).toBe("ongoing");
expect(error).not.toBeNull();
// 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(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
expect(error).toBeNull();
break;
case 4:
expect(firmwareUpdateContext).toEqual(aLatestFirmwareContext);
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("available-firmware");
expect(error).toBeNull();
done();
break;
}
}
catch (expectError) {
done(expectError);
}
step += 1;
},
});
});
});
describe("When another APDU/action was ongoing, and a Transport race condition occurred", () => {
it("should notify the function consumer of the need to unlock the device, and once done, continue the get latest available firmware flow", done => {
let count = 0;
// Returns a LockedDeviceError first, then the device info pretending the device has been unlocked
mockedInternalGetDeviceInfoTask.mockReturnValue(new rxjs_1.Observable(o => {
if (count < 1) {
count++;
// Mocks the internal task, some shared error are thrown (like `TransportRaceCondition`)
// and caught by the `sharedLogicTaskWrapper`
o.error(new errors_1.TransportRaceCondition());
}
else {
o.next({ type: "data", deviceInfo: aDeviceInfo });
}
}));
mockedGetLatestFirmwareTask.mockReturnValue((0, rxjs_1.of)({ type: "data", firmwareUpdateContext: aLatestFirmwareContext }));
let step = 1;
(0, getLatestAvailableFirmware_1.getLatestAvailableFirmwareAction)({
deviceId: "A_DEVICE_ID",
}).subscribe({
next: ({ firmwareUpdateContext, deviceInfo, lockedDevice, status, error, }) => {
try {
switch (step) {
case 1:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toBeNull();
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
expect(error).toBeNull();
break;
case 2:
expect(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toBeNull();
expect(lockedDevice).toBe(false);
// The actions is retrying its inner tasks on a locked device error
expect(status).toBe("ongoing");
expect(error).toEqual(expect.objectContaining({
type: "SharedError",
name: "TransportRaceCondition",
retrying: 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(firmwareUpdateContext).toBeNull();
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("ongoing");
expect(error).toBeNull();
break;
case 4:
expect(firmwareUpdateContext).toEqual(aLatestFirmwareContext);
expect(deviceInfo).toEqual(aDeviceInfo);
expect(lockedDevice).toBe(false);
expect(status).toBe("available-firmware");
expect(error).toBeNull();
done();
break;
}
}
catch (expectError) {
done(expectError);
}
step += 1;
},
});
});
});
});
//# sourceMappingURL=getLatestAvailableFirmware.test.js.map