add proper error handling for bad device id

This commit is contained in:
David Lechner
2021-01-22 16:22:54 -06:00
parent 3ec067e930
commit e5d98cdd84
2 changed files with 165 additions and 16 deletions
+158
View File
@@ -538,6 +538,78 @@ describe('flashFirmware', () => {
await saga.end();
});
test('unsupported device', async () => {
const metadata: FirmwareMetadata = {
'metadata-version': '1.0.0',
'device-id': HubType.MoveHub,
'checksum-type': 'sum',
'firmware-version': '1.2.3',
'max-firmware-size': 1024,
'mpy-abi-version': 5,
'mpy-cross-options': ['-mno-unicode'],
'user-mpy-offset': 100,
};
const zip = new JSZip();
zip.file('firmware-base.bin', new Uint8Array(64));
zip.file('firmware.metadata.json', JSON.stringify(metadata));
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
jest.spyOn(window, 'fetch').mockResolvedValueOnce(
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
saga.put(flashFirmwareAction());
// first step is to connect to the hub bootloader
let action = await saga.take();
expect(action).toEqual(connect());
saga.updateState({
bootloader: { connection: BootloaderConnectionState.Connected },
});
saga.put(didConnect());
// then find out what kind of hub it is
action = await saga.take();
expect(action).toEqual(infoRequest(0));
// received an unknown hub type ID
saga.put(didRequest(0));
saga.put(infoResponse(0x01000000, 0x08005000, 0x081f800, 0 as HubType));
// should raise an error that we don't have any firmware for this hub
action = await saga.take();
expect(action).toStrictEqual(
didFailToFinish(FailToFinishReasonType.NoFirmware),
);
// should request to disconnect after failure
action = await saga.take();
expect(action).toEqual(disconnect());
await saga.end();
});
test('erase response is failed', async () => {
const metadata: FirmwareMetadata = {
'metadata-version': '1.0.0',
@@ -1077,6 +1149,92 @@ describe('flashFirmware', () => {
await saga.end();
});
test('connected device type does not match firmware device type', async () => {
const metadata: FirmwareMetadata = {
'metadata-version': '1.0.0',
'device-id': HubType.MoveHub,
'checksum-type': 'sum',
'firmware-version': '1.2.3',
'max-firmware-size': 1024,
'mpy-abi-version': 5,
'mpy-cross-options': ['-mno-unicode'],
'user-mpy-offset': 100,
};
const zip = new JSZip();
zip.file('firmware-base.bin', new Uint8Array(64));
zip.file('firmware.metadata.json', JSON.stringify(metadata));
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
saga.put(
flashFirmwareAction(await zip.generateAsync({ type: 'arraybuffer' })),
);
// the first step is to compile main.py to .mpy
let action = await saga.take();
expect(action).toMatchInlineSnapshot(`
Object {
"options": Array [
"-mno-unicode",
],
"script": "print(\\"test\\")",
"type": "mpy.action.compile",
}
`);
const mpySize = 20;
const mpyBinaryData = new Uint8Array(mpySize);
saga.put(didCompile(mpyBinaryData));
// then connect to the hub bootloader
action = await saga.take();
expect(action).toEqual(connect());
saga.updateState({
bootloader: { connection: BootloaderConnectionState.Connected },
});
saga.put(didConnect());
// then find out what kind of hub it is
action = await saga.take();
expect(action).toEqual(infoRequest(0));
// connected hub type does not match firmware hub type
saga.put(didRequest(0));
saga.put(infoResponse(0x01000000, 0x08005000, 0x081f800, HubType.CityHub));
// should raise an error that we don't have any firmware for this hub
action = await saga.take();
expect(action).toStrictEqual(
didFailToFinish(FailToFinishReasonType.DeviceMismatch),
);
// should request to disconnect after failure
action = await saga.take();
expect(action).toEqual(disconnect());
await saga.end();
});
});
test('user supplied main.py', async () => {
+7 -16
View File
@@ -77,7 +77,7 @@ const firmwareZipMap = new Map<HubType, string>([
* Disconnects the BLE if we are connected and cancels the task (including the
* parent task).
*/
function* disconnectAndCancel(): SagaGenerator<never> {
function* disconnectAndCancel(): SagaGenerator<void> {
const connection = yield* select((s: RootState) => s.bootloader.connection);
if (connection === BootloaderConnectionState.Connected) {
@@ -85,12 +85,6 @@ function* disconnectAndCancel(): SagaGenerator<never> {
}
yield* cancel();
// HACK: cancel effect doesn't return, so we need this to make typescript
// happy about the never return type.
// istanbul ignore next: not reachable
throw undefined;
}
function* waitForDidRequest(id: number): SagaGenerator<BootloaderDidRequestAction> {
@@ -312,22 +306,19 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
});
if (deviceId !== undefined && info.hubType !== deviceId) {
throw Error(`Connected to ${info.hubType} but firmware is for ${deviceId}`);
yield* put(didFailToFinish(FailToFinishReasonType.DeviceMismatch));
yield* disconnectAndCancel();
}
if (firmware === undefined) {
const firmwarePath = firmwareZipMap.get(info.hubType);
if (firmwarePath === undefined) {
yield* put(
notification.add(
'error',
"Sorry, we don't have firmware for this hub yet.",
),
);
yield* put(disconnectRequest(nextMessageId()));
return;
yield* put(didFailToFinish(FailToFinishReasonType.NoFirmware));
yield* disconnectAndCancel();
}
defined(firmwarePath);
const response = yield* call(() => fetch(firmwarePath));
if (!response.ok) {
yield* put(notification.add('error', 'Failed to fetch firmware.'));