From ff03a8cbdf68868650f5aa4848d1d151f68c77b4 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 21 Jul 2025 11:26:27 -0700 Subject: [PATCH] Migrate rolodex to IndexedDB. Fix safe methods to only return once transaction is complete and requests committed. Update tests. --- src/lib/rolodex.ts | 172 +++++++++++++++----------- src/lib/workers/safe.ts | 98 ++++++++++++--- src/types.d.ts | 6 - test/test.manage-rolodex.mjs | 227 ++++++++++++++++++++++------------- 4 files changed, 326 insertions(+), 177 deletions(-) diff --git a/src/lib/rolodex.ts b/src/lib/rolodex.ts index c33b1c9..a7374b0 100644 --- a/src/lib/rolodex.ts +++ b/src/lib/rolodex.ts @@ -3,7 +3,7 @@ import { Account } from './account' import { bytes, utf8 } from './convert' -import { RolodexEntry } from '#types' +import { NamedData } from '#types' import { SafeWorker } from '#workers' /** @@ -11,8 +11,6 @@ import { SafeWorker } from '#workers' * saved under one nickname. */ export class Rolodex { - #entries: RolodexEntry[] = [] - /** * Adds an address to the rolodex under a specific nickname. * @@ -20,10 +18,11 @@ export class Rolodex { * the account exists under a different name, update the name. If no matches * are found at all, add a new entry. * - * @param {string} name - Alias for the address + * @param {string} name - Contact alias for the address * @param {string} address - Nano account address + * @returns {Promise} Promise for true if name and address are added to the rolodex, else false */ - async add (name: string, address: string): Promise { + static async add (name: string, address: string): Promise { if (name == null || name === '') { throw new Error('Name is required for rolodex entries') } @@ -46,88 +45,100 @@ export class Rolodex { .replaceAll('\\', '\\u005c') const account = Account.import(address) - const existingName = await this.getName(account.address) - if (existingName == null) { - const { result } = await SafeWorker.assign({ - method: 'store', - store: 'Rolodex', - password: '', - [address]: utf8.toBytes(name).buffer - }) - return result - } else if (existingName !== name) { - const { result: isDestroyed } = await SafeWorker.assign({ - method: 'destroy', - store: 'Rolodex', - name: address - }) - if (!isDestroyed) { - throw new Error('Failed to replace existing rolodex entry') + try { + const existingName = await this.getName(account.address) + if (existingName === name) { + return true } - const { result: isUpdated } = await SafeWorker.assign({ + const data: NamedData = { method: 'store', store: 'Rolodex', - password: '', + password: utf8.toBytes('').buffer, [address]: utf8.toBytes(name).buffer - }) - return isUpdated - } else { - const addresses = await this.getAddresses(name) - if (addresses.some(a => a === account.address)) { - return true - } else { - addresses.push(account.address) - const addressesJson = JSON.stringify(addresses) - const { result } = await SafeWorker.assign({ - method: 'store', - store: 'Rolodex', - password: '', - [name]: utf8.toBytes(addressesJson).buffer - }) - return result } + if (existingName != null) { + const filteredAddresses = (await this.getAddresses(existingName)).filter(a => a !== address).sort() + data[existingName] = utf8.toBytes(JSON.stringify(filteredAddresses)).buffer + } + const existingAddresses = await this.getAddresses(name) + existingAddresses.push(account.address) + data[name] = utf8.toBytes(JSON.stringify(existingAddresses)).buffer + const { result } = await SafeWorker.assign(data) + return result + } catch (err) { + throw new Error('failed to add address', { cause: err }) } } /** - * Gets the name associated with a specific Nano address from the rolodex. + * Removes a Nano address from its related contact in the rolodex. * * @param {string} address - Nano account address - * @returns {Promise} Promise for the name associated with the address, or null if not found + * @returns {Promise} Promise for true if address successfully removed, else false */ - async getName (address: string): Promise { - try { - const response = await SafeWorker.assign({ - method: 'fetch', - name: address, - store: 'Rolodex', - password: '' - }) - return bytes.toUtf8(new Uint8Array(response[address])) - } catch (err) { - console.log(err) - return null + static async deleteAddress (address: string): Promise { + const name = await this.getName(address) + if (name == null) { + return false + } + const addresses = (await this.getAddresses(name)).filter(a => a !== address).sort() + const { result: isUpdated } = await SafeWorker.assign({ + method: 'store', + store: 'Rolodex', + password: utf8.toBytes('').buffer, + [name]: utf8.toBytes(JSON.stringify(addresses)).buffer + }) + if (!isUpdated) { + throw new Error('failed to remove address from existing name') } + const { result } = await SafeWorker.assign({ + method: 'destroy', + store: 'Rolodex', + [address]: name + }) + return result } /** - * Gets all Nano addresses associated with a name from the rolodex. + * Removes a contact and its related Nano addresses from the rolodex. + * + * @param {string} name - Contact name to delete + * @returns {Promise} Promise for true if name and related addresses successfully removed, else false + */ + static async deleteName (name: string): Promise { + const data: NamedData = { + method: 'destroy', + store: 'Rolodex', + [name]: name + } + const addresses = await this.getAddresses(name) + for (const address of addresses) { + data[address] = name + } + const { result } = await SafeWorker.assign(data) + return result + } + + /** + * Gets all Nano account addresses associated with a name from the rolodex. * * @param {string} name - Alias to look up * @returns {Promise} Promise for a list of Nano addresses associated with the name */ - async getAddresses (name: string): Promise { + static async getAddresses (name: string): Promise { try { const response = await SafeWorker.assign({ method: 'fetch', name, store: 'Rolodex', - password: '' + password: utf8.toBytes('').buffer }) - const addressesJson = bytes.toUtf8(new Uint8Array(response[name])) - return JSON.parse(addressesJson) + const addresses = response[name] + return addresses + ? JSON.parse(bytes.toUtf8(new Uint8Array(addresses))).sort() + : [] } catch (err) { - console.log(err) + console.error(err) return [] } } @@ -137,21 +148,45 @@ export class Rolodex { * * @returns {Promise} Promise for a list of all names stored in the rolodex */ - async getAllNames (): Promise { + static async getAllNames (): Promise { try { const response = await SafeWorker.assign({ method: 'export', name: '', store: 'Rolodex', - password: '' + password: utf8.toBytes('').buffer }) return Object.keys(response).filter(v => v.slice(0, 5) !== 'nano_') } catch (err) { - console.log(err) + console.error(err) return [] } } + /** + * Gets the name associated with a specific Nano address from the rolodex. + * + * @param {string} address - Nano account address + * @returns {Promise} Promise for the name associated with the address, or null if not found + */ + static async getName (address: string): Promise { + try { + const response = await SafeWorker.assign({ + method: 'fetch', + name: address, + store: 'Rolodex', + password: utf8.toBytes('').buffer + }) + const name = response[address] + return name + ? bytes.toUtf8(new Uint8Array(name)) + : null + } catch (err) { + console.error(err) + return null + } + } + /** * Verifies whether the public key of any Nano address saved under a specific * name in the rolodex was used to sign a specific set of data. @@ -161,12 +196,12 @@ export class Rolodex { * @param {...string} data - Signed data to verify * @returns {Promise} True if the signature was used to sign the data, else false */ - async verify (name: string, signature: string, ...data: string[]): Promise { + static async verify (name: string, signature: string, ...data: string[]): Promise { const { verify } = await import('./tools.js') - const entries = this.#entries.filter(e => e.name === name) - for (const entry of entries) { - const key = entry.account.publicKey - const verified = await verify(key, signature, ...data) + const addresses = await this.getAddresses(name) + for (const address of addresses) { + const { publicKey } = Account.import(address) + const verified = await verify(publicKey, signature, ...data) if (verified) { return true } @@ -174,3 +209,4 @@ export class Rolodex { return false } } + diff --git a/src/lib/workers/safe.ts b/src/lib/workers/safe.ts index 7251688..12f05bd 100644 --- a/src/lib/workers/safe.ts +++ b/src/lib/workers/safe.ts @@ -51,14 +51,14 @@ export class Safe extends WorkerInterface { return await this.fetch(name, store, password, true) } case 'destroy': { - return { result: await this.destroy(name, store) } + return { result: await this.destroy(data, store) } } default: { throw new Error(`unknown Safe method ${method}`) } } } catch (err) { - console.log(err) + console.error(err) throw new Error('Safe error', { cause: err }) } } @@ -66,11 +66,11 @@ export class Safe extends WorkerInterface { /** * Removes data from the Safe without decrypting. */ - static async destroy (name: string, store: string): Promise { + static async destroy (data: NamedData, store: string): Promise { try { - return await this.#delete(name, store) + return await this.#delete(Object.keys(data), store) } catch (err) { - console.log(err) + console.error(err) throw new Error(this.ERR_MSG) } } @@ -131,8 +131,8 @@ export class Safe extends WorkerInterface { const records: SafeRecord[] = all ? await this.#getAll(store) : await this.#get(fields, store) - if (records == null || records.length === 0) { - throw new Error('Failed to find records') + if (records == null) { + throw new Error('') } const decryptionKeys: { [salt: string]: CryptoKey } = {} for (const record of records) { @@ -144,7 +144,7 @@ export class Safe extends WorkerInterface { } return results } catch (err) { - console.log(err) + console.error(err) throw new Error('Failed to get records', { cause: err }) } finally { bytes.erase(password) @@ -166,15 +166,33 @@ export class Safe extends WorkerInterface { return await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose]) } - static async #delete (name: string, store: string): Promise { + static async #delete (names: string[], store: string): Promise { const transaction = this.#storage.transaction(store, 'readwrite') const db = transaction.objectStore(store) return new Promise((resolve, reject) => { - const request = db.delete(name) - request.onsuccess = (event) => { - resolve((event.target as IDBRequest).result) + const requests = names.map(name => { + const request = db.delete(name) + request.onsuccess = (event) => { + console.log('delete request successful but not yet committed') + } + request.onerror = (event) => { + console.error('getAll request error before transaction committed') + } + return request + }) + transaction.oncomplete = (event) => { + console.log('delete transaction committed') + for (const request of requests) { + if (request?.error != null) { + reject(request.error) + } + if (request.result !== undefined) { + resolve(false) + } + } + resolve(true) } - request.onerror = (event) => { + transaction.onerror = (event) => { console.error('Database error') reject((event.target as IDBRequest).error) } @@ -185,9 +203,24 @@ export class Safe extends WorkerInterface { const transaction = this.#storage.transaction(store, 'readonly') const db = transaction.objectStore(store) return new Promise((resolve, reject) => { - const requests = names.map(name => db.get(name)) + const requests = names.map(name => { + const request = db.get(name) + request.onsuccess = (event) => { + console.log('get request successful but not yet committed') + } + request.onerror = (event) => { + console.error('get request error before transaction committed') + } + return request + }) transaction.oncomplete = (event) => { - const results = requests.map(r => r.result) + console.log('get transaction committed') + const results = [] + for (const request of requests) { + if (request?.error == null && request.result != null) { + results.push(request.result) + } + } resolve(results) } transaction.onerror = (event) => { @@ -203,9 +236,22 @@ export class Safe extends WorkerInterface { return new Promise((resolve, reject) => { const request = db.getAll() request.onsuccess = (event) => { - resolve((event.target as IDBRequest).result) + console.log('getAll request successful but transaction not yet committed') } request.onerror = (event) => { + console.error('getAll request error before transaction committed') + } + transaction.oncomplete = (event) => { + console.log('getAll transaction committed') + if (request?.error != null) { + reject(request.error) + } else if (request.result == null) { + reject('getAll request failed') + } else { + resolve(request.result) + } + } + transaction.onerror = (event) => { console.error('Database error') reject((event.target as IDBRequest).error) } @@ -246,9 +292,25 @@ export class Safe extends WorkerInterface { const transaction = this.#storage.transaction(store, 'readwrite') const db = transaction.objectStore(store) return new Promise((resolve, reject) => { - records.map(record => db.put(record, record.label)) + const requests = records.map(record => { + const request = db.put(record, record.label) + request.onsuccess = (event) => { + console.log('put request successful but not yet committed') + } + request.onerror = (event) => { + console.error('put request error before transaction committed') + } + return request + }) transaction.oncomplete = (event) => { - resolve((event.target as IDBRequest).error == null) + console.log('put transaction committed') + const results = [] + for (const request of requests) { + if (request?.error == null && request.result != null) { + results.push(request.result) + } + } + resolve(results.length > 0) } transaction.onerror = (event) => { console.error('Database error') diff --git a/src/types.d.ts b/src/types.d.ts index 8bc350a..fd44dd5 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -310,12 +310,6 @@ export declare const Bip44CkdWorker: Queue export declare const NanoNaClWorker: Queue export declare const SafeWorker: Queue - -export type RolodexEntry = { - name: string - account: Account -} - /** * Represents a Nano network node. It primarily consists of a URL which will * accept RPC calls, and an optional API key header construction can be passed if diff --git a/test/test.manage-rolodex.mjs b/test/test.manage-rolodex.mjs index c0b8a7b..b05394b 100644 --- a/test/test.manage-rolodex.mjs +++ b/test/test.manage-rolodex.mjs @@ -9,117 +9,174 @@ import { Rolodex, Tools } from '../dist/main.min.js' await suite('Rolodex valid contact management', async () => { - await test('should create a rolodex and add two contacts', async () => { - const rolodex = new Rolodex() - assert.equal(rolodex.constructor, Rolodex) - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - await rolodex.add('JaneSmith', NANO_TEST_VECTORS.ADDRESS_1) - - assert.equal(rolodex.getAllNames().length, 2) - assert.equal(rolodex.getAllNames()[0], 'JohnDoe') - assert.equal(rolodex.getAllNames()[1], 'JaneSmith') - assert.equal(rolodex.getAddresses('JohnDoe').length, 1) - assert.equal(rolodex.getAddresses('JohnDoe')[0], NANO_TEST_VECTORS.ADDRESS_0) - assert.equal(rolodex.getAddresses('JaneSmith').length, 1) - assert.equal(rolodex.getAddresses('JaneSmith')[0], NANO_TEST_VECTORS.ADDRESS_1) + await test('export returns empty array for empty rolodex (delete db if not already empty)', async () => { + const result = await Rolodex.getAllNames() + + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) }) - await test('should get a name from an address', async () => { - const rolodex = new Rolodex() - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - assert.equal(rolodex.getName(NANO_TEST_VECTORS.ADDRESS_0), 'JohnDoe') + await test('add two contacts, then delete them', async () => { + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) + await assert.resolves(Rolodex.add('JaneSmith', NANO_TEST_VECTORS.ADDRESS_1)) + + let names = await Rolodex.getAllNames() + assert.ok(Array.isArray(names)) + assert.equal(names.length, 2) + assert.equal(names[0], 'JaneSmith') + assert.equal(names[1], 'JohnDoe') + + const addressesJohnDoe = await Rolodex.getAddresses('JohnDoe') + const addressesJaneSmith = await Rolodex.getAddresses('JaneSmith') + + assert.ok(Array.isArray(addressesJohnDoe)) + assert.equal(addressesJohnDoe.length, 1) + assert.equal(addressesJohnDoe[0], NANO_TEST_VECTORS.ADDRESS_0) + assert.ok(Array.isArray(addressesJaneSmith)) + assert.equal(addressesJaneSmith.length, 1) + assert.equal(addressesJaneSmith[0], NANO_TEST_VECTORS.ADDRESS_1) + + const deleteJohnDoe = await Rolodex.deleteName('JohnDoe') + const deleteJaneSmith = await Rolodex.deleteName('JaneSmith') + assert.equal(deleteJohnDoe, true) + assert.equal(deleteJaneSmith, true) + + names = await Rolodex.getAllNames() + assert.ok(Array.isArray(names)) + assert.equal(names.length, 0) }) - await test('should add three addresses to the same contact', async () => { - const rolodex = new Rolodex() - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_1) - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_2) - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - assert.equal(rolodex.getAddresses('JohnDoe').length, 3) - assert.equal(rolodex.getAddresses('JohnDoe')[0], NANO_TEST_VECTORS.ADDRESS_1) - assert.equal(rolodex.getAddresses('JohnDoe')[1], NANO_TEST_VECTORS.ADDRESS_2) - assert.equal(rolodex.getAddresses('JohnDoe')[2], NANO_TEST_VECTORS.ADDRESS_0) + await test('get a name from an address', async () => { + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) + + const name = await Rolodex.getName(NANO_TEST_VECTORS.ADDRESS_0) + assert.equal(name, 'JohnDoe') + + await assert.resolves(Rolodex.deleteName('JohnDoe')) }) - await test('should update the name on an existing entry', async () => { - const rolodex = new Rolodex() - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - await rolodex.add('JaneSmith', NANO_TEST_VECTORS.ADDRESS_0) - assert.equal(rolodex.getAddresses('JohnDoe').length, 0) - assert.equal(rolodex.getAddresses('JaneSmith').length, 1) - assert.equal(rolodex.getAddresses('JaneSmith')[0], NANO_TEST_VECTORS.ADDRESS_0) + await test('add three addresses to the same contact, then delete one address, then delete the contact', async () => { + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_1)) + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_2)) + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) + + let addresses = await Rolodex.getAddresses('JohnDoe') + assert.ok(Array.isArray(addresses)) + assert.equal(addresses.length, 3) + assert.equal(addresses[0], NANO_TEST_VECTORS.ADDRESS_0) + assert.equal(addresses[1], NANO_TEST_VECTORS.ADDRESS_2) + assert.equal(addresses[2], NANO_TEST_VECTORS.ADDRESS_1) + + await assert.resolves(Rolodex.deleteAddress(NANO_TEST_VECTORS.ADDRESS_1)) + addresses = await Rolodex.getAddresses('JohnDoe') + assert.ok(Array.isArray(addresses)) + assert.equal(addresses.length, 2) + assert.equal(addresses[0], NANO_TEST_VECTORS.ADDRESS_0) + assert.equal(addresses[1], NANO_TEST_VECTORS.ADDRESS_2) + + await assert.resolves(Rolodex.deleteName('JohnDoe')) + addresses = await Rolodex.getAddresses('JohnDoe') + const address0 = await Rolodex.getName(NANO_TEST_VECTORS.ADDRESS_0) + const address1 = await Rolodex.getName(NANO_TEST_VECTORS.ADDRESS_1) + const address2 = await Rolodex.getName(NANO_TEST_VECTORS.ADDRESS_2) + + assert.ok(Array.isArray(addresses)) + assert.equal(addresses.length, 0) + assert.nullish(address0) + assert.nullish(address1) + assert.nullish(address2) }) - await test('should return empty address array for an unknown contact', async () => { - const rolodex = new Rolodex() - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - assert.equal(Array.isArray(rolodex.getAddresses('JaneSmith')), true) - assert.equal(rolodex.getAddresses('JaneSmith').length, 0) + await test('update the name on an existing address', async () => { + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) + await assert.resolves(Rolodex.add('JaneSmith', NANO_TEST_VECTORS.ADDRESS_0)) + const addressesJohnDoe = await Rolodex.getAddresses('JohnDoe') + const addressesJaneSmith = await Rolodex.getAddresses('JaneSmith') + + assert.ok(Array.isArray(addressesJohnDoe)) + assert.equal(addressesJohnDoe.length, 0) + assert.ok(Array.isArray(addressesJaneSmith)) + assert.equal(addressesJaneSmith.length, 1) + assert.equal(addressesJaneSmith[0], NANO_TEST_VECTORS.ADDRESS_0) + + await assert.resolves(Rolodex.deleteName('JohnDoe')) + await assert.resolves(Rolodex.deleteName('JaneSmith')) }) - await test('should return empty address array for blank contact names', () => { - const rolodex = new Rolodex() - //@ts-expect-error - assert.equal(Array.isArray(rolodex.getAddresses(undefined)), true) - //@ts-expect-error - assert.equal(rolodex.getAddresses(undefined).length, 0) + await test('return empty address array for an unknown contact', async () => { + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) + const addressesJaneSmith = await Rolodex.getAddresses('JaneSmith') + + assert.equal(Array.isArray(addressesJaneSmith), true) + assert.equal(addressesJaneSmith.length, 0) + + await assert.resolves(Rolodex.deleteName('JohnDoe')) + }) + + await test('return empty address array for blank contact names', async () => { //@ts-expect-error - assert.equal(Array.isArray(rolodex.getAddresses(null)), true) + const addressesUndefined = await Rolodex.getAddresses(undefined) //@ts-expect-error - assert.equal(rolodex.getAddresses(null).length, 0) - assert.equal(Array.isArray(rolodex.getAddresses('')), true) - assert.equal(rolodex.getAddresses('').length, 0) + const addressesNull = await Rolodex.getAddresses(null) + const addressesBlank = await Rolodex.getAddresses('') + + assert.equal(Array.isArray(addressesUndefined), true) + assert.equal(addressesUndefined.length, 0) + assert.equal(Array.isArray(addressesNull), true) + assert.equal(addressesNull.length, 0) + assert.equal(Array.isArray(addressesBlank), true) + assert.equal(addressesBlank.length, 0) }) await test('should return null for an unknown address', async () => { - const rolodex = new Rolodex() - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - assert.ok(rolodex.getName(NANO_TEST_VECTORS.ADDRESS_1) === null) - assert.ok(rolodex.getName(NANO_TEST_VECTORS.ADDRESS_1) !== undefined) + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) + const addressUnknown = await Rolodex.getName(NANO_TEST_VECTORS.ADDRESS_1) + + assert.ok(addressUnknown === null) + assert.ok(addressUnknown !== undefined) + + await assert.resolves(Rolodex.deleteName('JohnDoe')) }) await test('should return null for a blank address', async () => { - const rolodex = new Rolodex() - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - //@ts-expect-error - assert.ok(rolodex.getName(undefined) === null) - //@ts-expect-error - assert.ok(rolodex.getName(undefined) !== undefined) + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) //@ts-expect-error - assert.ok(rolodex.getName(null) === null) + const nameUndefined = await Rolodex.getName(undefined) //@ts-expect-error - assert.ok(rolodex.getName(null) !== undefined) - assert.ok(rolodex.getName('') === null) - assert.ok(rolodex.getName('') !== undefined) - }) -}) + const nameNull = await Rolodex.getName(null) + const nameBlank = await Rolodex.getName('') -await suite('Rolodex exceptions', async () => { + assert.ok(nameUndefined === null) + assert.ok(nameUndefined !== undefined) + assert.ok(nameNull === null) + assert.ok(nameNull !== undefined) + assert.ok(nameBlank === null) + assert.ok(nameBlank !== undefined) + + await assert.resolves(Rolodex.deleteName('JohnDoe')) + }) - await test('should throw if adding no data', async () => { - const rolodex = new Rolodex() + await test('throw if adding no data', async () => { //@ts-expect-error - await assert.rejects(rolodex.add()) + await assert.rejects(Rolodex.add()) }) - await test('should throw if passed no address', async () => { - const rolodex = new Rolodex() + await test('throw if passed no address', async () => { //@ts-expect-error - await assert.rejects(rolodex.add('JohnDoe')) + await assert.rejects(Rolodex.add('JohnDoe')) //@ts-expect-error - await assert.rejects(rolodex.add('JohnDoe', undefined)) + await assert.rejects(Rolodex.add('JohnDoe', undefined)) //@ts-expect-error - await assert.rejects(rolodex.add('JohnDoe', null)) - await assert.rejects(rolodex.add('JohnDoe', '')) + await assert.rejects(Rolodex.add('JohnDoe', null)) + await assert.rejects(Rolodex.add('JohnDoe', '')) }) - await test('should throw if name is blank', async () => { - const rolodex = new Rolodex() + await test('throw if name is blank', async () => { //@ts-expect-error - await assert.rejects(rolodex.add(undefined, NANO_TEST_VECTORS.ADDRESS_0)) + await assert.rejects(Rolodex.add(undefined, NANO_TEST_VECTORS.ADDRESS_0)) //@ts-expect-error - await assert.rejects(rolodex.add(null, NANO_TEST_VECTORS.ADDRESS_0)) - await assert.rejects(rolodex.add('', NANO_TEST_VECTORS.ADDRESS_0)) + await assert.rejects(Rolodex.add(null, NANO_TEST_VECTORS.ADDRESS_0)) + await assert.rejects(Rolodex.add('', NANO_TEST_VECTORS.ADDRESS_0)) }) }) @@ -128,18 +185,18 @@ await suite('Rolodex data signature verification', async () => { await test('should verify valid data and signature', async () => { const data = 'Test data' const signature = await Tools.sign(NANO_TEST_VECTORS.PRIVATE_0, data) - const rolodex = new Rolodex() - await rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0) - const result = await rolodex.verify('JohnDoe', signature, data) + await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0)) + const result = await Rolodex.verify('JohnDoe', signature, data) + await assert.resolves(Rolodex.deleteName('JohnDoe')) assert.equal(result, true) }) await test('should reject incorrect contact for signature', async () => { const data = 'Test data' const signature = await Tools.sign(NANO_TEST_VECTORS.PRIVATE_0, data) - const rolodex = new Rolodex() - await rolodex.add('JaneSmith', NANO_TEST_VECTORS.ADDRESS_1) - const result = await rolodex.verify('JaneSmith', signature, data) + await assert.resolves(Rolodex.add('JaneSmith', NANO_TEST_VECTORS.ADDRESS_1)) + const result = await Rolodex.verify('JaneSmith', signature, data) + await assert.resolves(Rolodex.deleteName('JaneSmith')) assert.equal(result, false) }) }) -- 2.47.3