From d0f14f83f9323d8f0f5798704367c6cc872ee892 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Mon, 2 Dec 2019 22:29:42 -0300 Subject: [PATCH] Add test for IndexedDB --- jest.setup.js | 1 + package-lock.json | 138 ++++++++++++++++++++++++++++++++++++ package.json | 2 + src/client/crypto/index.ts | 0 src/client/db/index.test.ts | 46 ++++++++++++ src/client/db/index.ts | 2 + src/client/db/open.ts | 12 ++++ src/client/db/promisify.ts | 16 +++++ src/client/db/upgrade.ts | 12 ++++ 9 files changed, 229 insertions(+) create mode 100644 src/client/crypto/index.ts create mode 100644 src/client/db/index.test.ts create mode 100644 src/client/db/index.ts create mode 100644 src/client/db/open.ts create mode 100644 src/client/db/promisify.ts create mode 100644 src/client/db/upgrade.ts diff --git a/jest.setup.js b/jest.setup.js index e69de29..a107b7e 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -0,0 +1 @@ +require('fake-indexeddb/auto') diff --git a/package-lock.json b/package-lock.json index 7aae6cc..80e9855 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2279,6 +2279,43 @@ "@types/yargs": "^12.0.9" } }, + "@trust/keyto": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@trust/keyto/-/keyto-0.3.7.tgz", + "integrity": "sha512-t5kWWCTkPgg24JWVuCTPMx7l13F7YHdxBeJkT1vmoHjROgiOIEAN8eeY+iRmP1Hwsx+S7U55HyuqSsECr08a8A==", + "dev": true, + "requires": { + "asn1.js": "^5.0.1", + "base64url": "^3.0.1", + "elliptic": "^6.4.1" + }, + "dependencies": { + "asn1.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.2.0.tgz", + "integrity": "sha512-Q7hnYGGNYbcmGrCPulXfkEw7oW7qjWeM4ZTALmgpuIcZLxyqqKYWxCZg2UBm8bklrnB4m2mGyJPWfoktdORD8A==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + } + } + }, + "@trust/webcrypto": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.9.2.tgz", + "integrity": "sha512-5iMAVcGYKhqLJGjefB1nzuQSqUJTru0nG4CytpBT/GGp1Piz/MVnj2jORdYf4JBYzggCIa8WZUr2rchP2Ngn/w==", + "dev": true, + "requires": { + "@trust/keyto": "^0.3.4", + "base64url": "^3.0.0", + "elliptic": "^6.4.0", + "node-rsa": "^0.4.0", + "text-encoding": "^0.6.1" + } + }, "@types/babel__core": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", @@ -3843,6 +3880,12 @@ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" }, + "base64-arraybuffer-es6": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.5.0.tgz", + "integrity": "sha512-UCIPaDJrNNj5jG2ZL+nzJ7czvZV/ZYX6LaIRgfVU1k1edJOQg7dkbiSKzwHkNp6aHEHER/PhlFBrMYnlvJJQEw==", + "dev": true + }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", @@ -3854,6 +3897,12 @@ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -6109,6 +6158,16 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, + "fake-indexeddb": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-3.0.0.tgz", + "integrity": "sha512-VrnV9dJWlVWvd8hp9MMR+JS4RLC4ZmToSkuCg91ZwpYE5mSODb3n5VEaV62Hf3AusnbrPfwQhukU+rGZm5W8PQ==", + "dev": true, + "requires": { + "realistic-structured-clone": "^2.0.1", + "setimmediate": "^1.0.5" + } + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -9922,6 +9981,23 @@ } } }, + "node-rsa": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz", + "integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=", + "dev": true, + "requires": { + "asn1": "0.2.3" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + } + } + }, "node-sass": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz", @@ -11279,6 +11355,26 @@ } } }, + "realistic-structured-clone": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-2.0.2.tgz", + "integrity": "sha512-5IEvyfuMJ4tjQOuKKTFNvd+H9GSbE87IcendSBannE28PTrbolgaVg5DdEApRKhtze794iXqVUFKV60GLCNKEg==", + "dev": true, + "requires": { + "core-js": "^2.5.3", + "domexception": "^1.0.1", + "typeson": "^5.8.2", + "typeson-registry": "^1.0.0-alpha.20" + }, + "dependencies": { + "core-js": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", + "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==", + "dev": true + } + } + }, "realpath-native": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", @@ -11843,6 +11939,12 @@ } } }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -12774,6 +12876,12 @@ } } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13134,6 +13242,36 @@ "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, + "typeson": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/typeson/-/typeson-5.13.0.tgz", + "integrity": "sha512-xcSaSt+hY/VcRYcqZuVkJwMjDXXJb4CZd51qDocpYw8waA314ygyOPlKhsGsw4qKuJ0tfLLUrxccrm+xvyS0AQ==", + "dev": true + }, + "typeson-registry": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.29.tgz", + "integrity": "sha512-DqRoIx0CtmBGXuumFk7ex5QE6JCWHNKry6D1psGUUB9uIPRrj/SCtuRAidZjLgieWpwn1EfrTFtG0IN2t//F8A==", + "dev": true, + "requires": { + "base64-arraybuffer-es6": "0.5.0", + "typeson": "5.13.0", + "whatwg-url": "7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, "uglify-js": { "version": "3.6.9", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", diff --git a/package.json b/package.json index c7f232d..ce402d7 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@babel/core": "^7.7.2", "@babel/polyfill": "^7.7.0", "@babel/preset-env": "^7.7.1", + "@trust/webcrypto": "^0.9.2", "@types/classnames": "^2.2.9", "@types/debug": "^4.1.5", "@types/ejs": "^2.6.3", @@ -91,6 +92,7 @@ "eslint": "^6.6.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-react": "^7.16.0", + "fake-indexeddb": "^3.0.0", "jest": "^24.9.0", "loose-envify": "^1.4.0", "node-sass": "^4.13.0", diff --git a/src/client/crypto/index.ts b/src/client/crypto/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/client/db/index.test.ts b/src/client/db/index.test.ts new file mode 100644 index 0000000..f27503b --- /dev/null +++ b/src/client/db/index.test.ts @@ -0,0 +1,46 @@ +import { open, promisify } from './index' + +describe('db', () => { + + const TEST_DB = 'TEST_DB' + + async function getError(promise: Promise): Promise { + let error: Error + try { + await promise + } catch (err) { + error = err + } + expect(error!).toBeTruthy() + return error! + } + + afterEach(async () => { + db && db.close() + await promisify(window.indexedDB.deleteDatabase(TEST_DB)) + }) + let db: IDBDatabase + + describe('open', () => { + + it('can use a custom upgrade function', async () => { + let called = false + db = await open(TEST_DB, 1, ev => { + called = true + }) + expect(called).toBe(true) + }) + + it('opens a new database and upgrades it', async () => { + db = await open(TEST_DB, 1) + const tx = db.transaction('identities', 'readwrite') + const store = tx.objectStore('identities') + await promisify(store.put({id: 'test'})) + const value = await promisify(store.get('test')) + expect(value).toEqual({id: 'test'}) + await promisify(tx) + }) + + }) + +}) diff --git a/src/client/db/index.ts b/src/client/db/index.ts new file mode 100644 index 0000000..34ddef4 --- /dev/null +++ b/src/client/db/index.ts @@ -0,0 +1,2 @@ +export { promisify } from './promisify' +export { open } from './open' diff --git a/src/client/db/open.ts b/src/client/db/open.ts new file mode 100644 index 0000000..a32246e --- /dev/null +++ b/src/client/db/open.ts @@ -0,0 +1,12 @@ +import { promisify } from './promisify' +import { Upgrade, upgrade } from './upgrade' + +export async function open( + name: string, + version: number, + doUpgrade: Upgrade = upgrade, +) { + const request = window.indexedDB.open(name, version) + request.onupgradeneeded = doUpgrade + return promisify(request) +} diff --git a/src/client/db/promisify.ts b/src/client/db/promisify.ts new file mode 100644 index 0000000..03201f9 --- /dev/null +++ b/src/client/db/promisify.ts @@ -0,0 +1,16 @@ +export async function promisify(request: IDBTransaction): Promise +export async function promisify(request: IDBRequest): Promise +export async function promisify(request: IDBRequest | IDBTransaction) { + if ('oncomplete' in request) { + // this is a transaction + return new Promise((resolve, reject) => { + request.oncomplete = () => resolve() + request.onerror = err => reject(err) + }) + } + // this is an IDBRequest + return new Promise((resolve, reject) => { + request.onsuccess = () => resolve(request.result) + request.onerror = err => reject(err) + }) +} diff --git a/src/client/db/upgrade.ts b/src/client/db/upgrade.ts new file mode 100644 index 0000000..688a562 --- /dev/null +++ b/src/client/db/upgrade.ts @@ -0,0 +1,12 @@ +export type Upgrade = + (this: IDBOpenDBRequest, event: IDBVersionChangeEvent) => void + +export function upgrade(this: IDBOpenDBRequest, event: IDBVersionChangeEvent) { + const db = this.result + switch (event.oldVersion) { + case 0: + db.createObjectStore('identities', { + keyPath: 'id', + }) + } +}