From 664fc2a2ec1a0a9df3e985658522dab7c4f72bd9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 23 Aug 2024 16:42:47 +0200 Subject: [PATCH] Improve error messaging --- apps/extension/src/storage/base.ts | 30 ++-- .../storage/migrations/base-migration.test.ts | 153 ++++++++++++++++++ 2 files changed, 170 insertions(+), 13 deletions(-) diff --git a/apps/extension/src/storage/base.ts b/apps/extension/src/storage/base.ts index 9f6231d9..87c917e2 100644 --- a/apps/extension/src/storage/base.ts +++ b/apps/extension/src/storage/base.ts @@ -184,19 +184,23 @@ export class ExtensionStorage { * Initializes the database with defaults or performs migrations (multiple possible if a sequence is needed). */ private async migrateOrInitializeIfNeeded(): Promise { - // If db is empty, initialize it with defaults. - const bytesInUse = await this.storage.getBytesInUse(); - if (bytesInUse === 0) { - const allDefaults = { ...this.defaults, dbVersion: this.version.current }; - // @ts-expect-error Typescript does not know how to combine the above types - await this._set(allDefaults); - return; - } - - let storedVersion = (await this._get('dbVersion')) ?? 0; // default to zero - // If stored version is not the same, keep migrating versions until current - while (storedVersion !== this.version.current) { - storedVersion = await this.migrateAllFields(storedVersion); + try { + // If db is empty, initialize it with defaults. + const bytesInUse = await this.storage.getBytesInUse(); + if (bytesInUse === 0) { + const allDefaults = { ...this.defaults, dbVersion: this.version.current }; + // @ts-expect-error Typescript does not know how to combine the above types + await this._set(allDefaults); + return; + } + + let storedVersion = (await this._get('dbVersion')) ?? 0; // default to zero + // If stored version is not the same, keep migrating versions until current + while (storedVersion !== this.version.current) { + storedVersion = await this.migrateAllFields(storedVersion); + } + } catch (e) { + throw new Error(`There was an error with migrating the database: ${String(e)}`); } } } diff --git a/apps/extension/src/storage/migrations/base-migration.test.ts b/apps/extension/src/storage/migrations/base-migration.test.ts index cc0c69e1..55c3371d 100644 --- a/apps/extension/src/storage/migrations/base-migration.test.ts +++ b/apps/extension/src/storage/migrations/base-migration.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'; import { MockStorageArea } from '../mock'; import { ExtensionStorage, RequiredMigrations } from '../base'; +import { localV0Migration } from './local-v1-migration'; interface MockV0State { network: string; @@ -295,5 +296,157 @@ describe('Storage migrations', () => { const result = await v2ExtStorage.get('newField'); expect(result).toBeUndefined(); }); + + test('error during migration from v0 to v1', async () => { + const faultyMigration = new ExtensionStorage({ + storage: rawStorage, + defaults: { + network: '', + accounts: [], + seedPhrase: [], + frontend: 'http://default.com', + grpcUrl: { url: '' }, + fullSyncHeight: 0, + }, + version: { + current: 1, + migrations: { + 0: () => { + throw new Error('network request error 404'); + }, + }, + }, + }); + + const mock0StorageState: Record = { + network: '', + accounts: [], + seedPhrase: 'cat dog mouse horse', + frontend: 'http://default.com', + grpcUrl: 'grpc.void.test', + fullSyncHeight: 0, + } satisfies MockV0State; + await rawStorage.set(mock0StorageState); + + await expect(faultyMigration.get('network')).rejects.toThrow( + 'There was an error with migrating the database: Error: network request error 404', + ); + }); + + test('error during migration from v1 to v2', async () => { + const mock1Storage = new ExtensionStorage({ + storage: rawStorage, + defaults: { + network: '', + accounts: [], + seedPhrase: [], + frontend: 'http://default.com', + grpcUrl: { url: '' }, + fullSyncHeight: 0, + }, + version: { + current: 1, + migrations: { + 0: localV0Migration, + }, + }, + }); + + await mock1Storage.set('fullSyncHeight', 123); + const height = await mock1Storage.get('fullSyncHeight'); + expect(height).toEqual(123); + + const faultyMigration = new ExtensionStorage({ + storage: rawStorage, + defaults: { + network: '', + accounts: [], + seedPhrase: [], + frontend: 'http://default.com', + grpcUrl: { url: '', image: '' }, + fullSyncHeight: 0n, + }, + version: { + current: 2, + migrations: { + 0: localV0Migration, + 1: () => { + throw new Error('network request error 502'); + }, + }, + }, + }); + + await expect(faultyMigration.get('network')).rejects.toThrow( + 'There was an error with migrating the database: Error: network request error 502', + ); + }); + + test('error during migration propagates to multiple callers', async () => { + const originalNetworkVal = 'original.void.zone'; + const mock1Storage = new ExtensionStorage({ + storage: rawStorage, + defaults: { + network: originalNetworkVal, + accounts: [], + seedPhrase: [], + frontend: 'http://default.com', + grpcUrl: { url: '' }, + fullSyncHeight: 0, + }, + version: { + current: 1, + migrations: { + 0: localV0Migration, + }, + }, + }); + + await mock1Storage.set('fullSyncHeight', 123); + const height = await mock1Storage.get('fullSyncHeight'); + expect(height).toEqual(123); + + const faultyMigration = new ExtensionStorage({ + storage: rawStorage, + defaults: { + network: '', + accounts: [], + seedPhrase: [], + frontend: 'http://default.com', + grpcUrl: { url: '', image: '' }, + fullSyncHeight: 0n, + }, + version: { + current: 2, + migrations: { + 0: localV0Migration, + 1: () => { + throw new Error('network request error 502'); + }, + }, + }, + }); + + const expectedError = + 'There was an error with migrating the database: Error: network request error 502'; + + const callA = faultyMigration.get('network'); + await expect(callA).rejects.toThrow(expectedError); + const rawValueA = await rawStorage.get('network'); + expect(rawValueA).toStrictEqual({ network: originalNetworkVal }); + + const callB = faultyMigration.set('network', 'xyz'); + await expect(callB).rejects.toThrow(expectedError); + const rawValueB = await rawStorage.get('network'); + expect(rawValueB).toStrictEqual({ network: originalNetworkVal }); + + const callC = faultyMigration.get('network'); + await expect(callC).rejects.toThrow(expectedError); + const rawValueC = await rawStorage.get('network'); + expect(rawValueC).toStrictEqual({ network: originalNetworkVal }); + + const callD = faultyMigration.get('accounts'); + await expect(callD).rejects.toThrow(expectedError); + }); }); });