From 3208faa7e6db5025854e025b4585ec9fe8f5d62c Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 22 Jan 2021 17:44:10 -0600 Subject: [PATCH] add proper error handling for fail to fetch --- src/actions/flash-firmware.ts | 26 +++++++++++- src/sagas/flash-firmware.test.ts | 71 ++++++++++++++++++++++++++++++++ src/sagas/flash-firmware.ts | 10 ++--- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/actions/flash-firmware.ts b/src/actions/flash-firmware.ts index f28f8519..cda81bac 100644 --- a/src/actions/flash-firmware.ts +++ b/src/actions/flash-firmware.ts @@ -53,6 +53,8 @@ export enum FailToFinishReasonType { NoFirmware = 'flashFirmware.failToFinish.reason.noFirmware', /** The provided firmware.zip does not match the connected hub. */ DeviceMismatch = 'flashFirmware.failToFinish.reason.deviceMismatch', + /** Failed to fetch firmware from the server. */ + FailedToFetch = 'flashFirmware.failToFinish.reason.failedToFetch', /** There was a problem with the zip file. */ ZipError = 'flashFirmware.failToFinish.reason.zipError', /** Metadata property is missing or invalid. */ @@ -87,6 +89,10 @@ export type FailToFinishReasonNoFirmware = Reason; +export type FailToFinishReasonFailedToFetch = Reason & { + response: Response; +}; + export type FailToFinishReasonZipError = Reason & { err: FirmwareReaderError; }; @@ -112,6 +118,7 @@ export type FailToFinishReason = | FailToFinishReasonHubError | FailToFinishReasonNoFirmware | FailToFinishReasonDeviceMismatch + | FailToFinishReasonFailedToFetch | FailToFinishReasonZipError | FailToFinishReasonBadMetadata | FailToFinishReasonFirmwareSize @@ -183,6 +190,11 @@ export function didFailToFinish( hubError: HubError, ): FlashFirmwareDidFailToFinishAction; +export function didFailToFinish( + reason: FailToFinishReasonType.FailedToFetch, + response: Response, +): FlashFirmwareDidFailToFinishAction; + export function didFailToFinish( reason: FailToFinishReasonType.ZipError, err: FirmwareReaderError, @@ -204,6 +216,7 @@ export function didFailToFinish( FailToFinishReasonType, | FailToFinishReasonType.BleError | FailToFinishReasonType.HubError + | FailToFinishReasonType.FailedToFetch | FailToFinishReasonType.ZipError | FailToFinishReasonType.BadMetadata | FailToFinishReasonType.Unknown @@ -216,7 +229,7 @@ export function didFailToFinish( */ export function didFailToFinish( reason: FailToFinishReasonType, - arg1?: string | HubError | Error, + arg1?: string | HubError | Error | Response, arg2?: MetadataProblem, ): FlashFirmwareDidFailToFinishAction { if (reason === FailToFinishReasonType.BleError) { @@ -241,6 +254,17 @@ export function didFailToFinish( }; } + if (reason === FailToFinishReasonType.FailedToFetch) { + // istanbul ignore if: programmer error give wrong arg + if (!(arg1 instanceof Response)) { + throw new Error('missing or invalid response'); + } + return { + type: FlashFirmwareActionType.DidFailToFinish, + reason: { reason, response: arg1 }, + }; + } + if (reason === FailToFinishReasonType.ZipError) { // istanbul ignore if: programmer error give wrong arg if (!(arg1 instanceof FirmwareReaderError)) { diff --git a/src/sagas/flash-firmware.test.ts b/src/sagas/flash-firmware.test.ts index 5d0e3c37..240ed60f 100644 --- a/src/sagas/flash-firmware.test.ts +++ b/src/sagas/flash-firmware.test.ts @@ -541,6 +541,77 @@ describe('flashFirmware', () => { await saga.end(); }); + test('failed to fetch firmware', 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 response = new Response(undefined, { status: 404 }); + jest.spyOn(window, 'fetch').mockResolvedValueOnce(response); + + 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, HubType.MoveHub)); + + // should raise an error that we don't have any firmware for this hub + + action = await saga.take(); + expect(action).toStrictEqual( + didFailToFinish(FailToFinishReasonType.FailedToFetch, response), + ); + + // should request to disconnect after failure + + action = await saga.take(); + expect(action).toEqual(disconnect()); + + await saga.end(); + }); + test('connected device does not match firmware device', async () => { const metadata: FirmwareMetadata = { 'metadata-version': '1.0.0', diff --git a/src/sagas/flash-firmware.ts b/src/sagas/flash-firmware.ts index 06ee3575..218c5cea 100644 --- a/src/sagas/flash-firmware.ts +++ b/src/sagas/flash-firmware.ts @@ -47,7 +47,6 @@ import { checksumRequest, connect, disconnect, - disconnectRequest, eraseRequest, infoRequest, initRequest, @@ -60,7 +59,6 @@ import { MpyDidFailToCompileAction, compile, } from '../actions/mpy'; -import * as notification from '../actions/notification'; import { MaxProgramFlashSize, Result } from '../protocols/lwp3-bootloader'; import { RootState } from '../reducers'; import { BootloaderConnectionState } from '../reducers/bootloader'; @@ -322,10 +320,10 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator { const response = yield* call(() => fetch(firmwarePath)); if (!response.ok) { - yield* put(notification.add('error', 'Failed to fetch firmware.')); - const disconnectAction = yield* put(disconnectRequest(nextMessageId())); - yield* waitForDidRequest(disconnectAction.id); - return; + yield* put( + didFailToFinish(FailToFinishReasonType.FailedToFetch, response), + ); + yield* disconnectAndCancel(); } const data = yield* call(() => response.arrayBuffer());