diff --git a/spec/unit/stores/indexeddb.spec.ts b/spec/unit/stores/indexeddb.spec.ts new file mode 100644 index 00000000000..06e3097ea52 --- /dev/null +++ b/spec/unit/stores/indexeddb.spec.ts @@ -0,0 +1,72 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import 'fake-indexeddb/auto'; +import 'jest-localstorage-mock'; + +import { IndexedDBStore, IStateEventWithRoomId } from "../../../src"; +import { emitPromise } from "../../test-utils/test-utils"; +import { LocalIndexedDBStoreBackend } from "../../../src/store/indexeddb-local-backend"; + +describe("IndexedDBStore", () => { + it("should degrade to MemoryStore on IDB errors", async () => { + const roomId = "!room:id"; + const store = new IndexedDBStore({ + indexedDB: indexedDB, + dbName: "database", + localStorage, + }); + await store.startup(); + + const member1: IStateEventWithRoomId = { + room_id: roomId, + event_id: "!ev1:id", + sender: "@user1:id", + state_key: "@user1:id", + type: "m.room.member", + origin_server_ts: 123, + content: {}, + }; + const member2: IStateEventWithRoomId = { + room_id: roomId, + event_id: "!ev2:id", + sender: "@user2:id", + state_key: "@user2:id", + type: "m.room.member", + origin_server_ts: 123, + content: {}, + }; + + expect(await store.getOutOfBandMembers(roomId)).toBe(null); + await store.setOutOfBandMembers(roomId, [member1]); + expect(await store.getOutOfBandMembers(roomId)).toHaveLength(1); + + // Simulate a broken IDB + (store.backend as LocalIndexedDBStoreBackend)["db"].transaction = (): IDBTransaction => { + const err = new Error("Failed to execute 'transaction' on 'IDBDatabase': " + + "The database connection is closing."); + err.name = "InvalidStateError"; + throw err; + }; + + expect(await store.getOutOfBandMembers(roomId)).toHaveLength(1); + await Promise.all([ + emitPromise(store["emitter"], "degraded"), + store.setOutOfBandMembers(roomId, [member1, member2]), + ]); + expect(await store.getOutOfBandMembers(roomId)).toHaveLength(2); + }); +}); diff --git a/src/store/indexeddb-local-backend.ts b/src/store/indexeddb-local-backend.ts index a044a95c48c..178931ff857 100644 --- a/src/store/indexeddb-local-backend.ts +++ b/src/store/indexeddb-local-backend.ts @@ -268,7 +268,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { reject(err); }; }).then((events) => { - logger.log(`LL: got ${events && events.length} membershipEvents from storage for room ${roomId} ...`); + logger.log(`LL: got ${events?.length} membershipEvents from storage for room ${roomId} ...`); return events; }); } diff --git a/src/store/indexeddb.ts b/src/store/indexeddb.ts index 09b54bf29cb..7d6e3f17c22 100644 --- a/src/store/indexeddb.ts +++ b/src/store/indexeddb.ts @@ -243,7 +243,7 @@ export class IndexedDBStore extends MemoryStore { * @returns {event[]} the events, potentially an empty array if OOB loading didn't yield any new members * @returns {null} in case the members for this room haven't been stored yet */ - public getOutOfBandMembers = this.degradable((roomId: string): Promise => { + public getOutOfBandMembers = this.degradable((roomId: string): Promise => { return this.backend.getOutOfBandMembers(roomId); }, "getOutOfBandMembers"); @@ -320,7 +320,7 @@ export class IndexedDBStore extends MemoryStore { // `IndexedDBStore` also maintain the state that `MemoryStore` uses (many are // not overridden at all). if (fallbackFn) { - return fallbackFn(...args); + return fallbackFn.call(this, ...args); } } };