diff --git a/.gitignore b/.gitignore index a74bb619d..5402e749a 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,6 @@ jspm_packages/ dist/ -.history \ No newline at end of file +.history + +storage.json \ No newline at end of file diff --git a/src/managers/AccountManager.ts b/src/managers/AccountManager.ts index 7a2cf19e3..6a9e6e85a 100644 --- a/src/managers/AccountManager.ts +++ b/src/managers/AccountManager.ts @@ -31,7 +31,19 @@ export class AccountManager { const accounts = await this.storage.get(StorageKey.ACCOUNTS) const filteredAccounts = accounts.filter( - (accountInfo) => accountInfo.accountIdentifier !== accountIdentifier + (account) => account.accountIdentifier !== accountIdentifier + ) + + return this.storage.set(StorageKey.ACCOUNTS, filteredAccounts) + } + + public async removeAccounts(accountIdentifiers: string[]): Promise { + const accounts = await this.storage.get(StorageKey.ACCOUNTS) + + const filteredAccounts = accounts.filter((account) => + accountIdentifiers.every( + (accountIdentifier) => account.accountIdentifier !== accountIdentifier + ) ) return this.storage.set(StorageKey.ACCOUNTS, filteredAccounts) diff --git a/src/managers/PermissionManager.ts b/src/managers/PermissionManager.ts index 1f5162c09..4168b421c 100644 --- a/src/managers/PermissionManager.ts +++ b/src/managers/PermissionManager.ts @@ -46,6 +46,18 @@ export class PermissionManager { return this.storage.set(StorageKey.PERMISSION_LIST, filteredPermissions) } + public async removePermissions(accountIdentifiers: string[]): Promise { + const permissions: PermissionInfo[] = await this.storage.get(StorageKey.PERMISSION_LIST) + + const filteredPermissions = permissions.filter((permission) => + accountIdentifiers.every( + (accountIdentifier) => permission.accountIdentifier !== accountIdentifier + ) + ) + + return this.storage.set(StorageKey.PERMISSION_LIST, filteredPermissions) + } + public async removeAllPermissions(): Promise { return this.storage.delete(StorageKey.PERMISSION_LIST) } diff --git a/test/managers/AccountManager.spec.ts b/test/managers/AccountManager.spec.ts new file mode 100644 index 000000000..26fd885db --- /dev/null +++ b/test/managers/AccountManager.spec.ts @@ -0,0 +1,158 @@ +import * as chai from 'chai' +import * as chaiAsPromised from 'chai-as-promised' +import 'mocha' +import { AccountManager } from '../../src/managers/AccountManager' + +import { AccountInfo, Origin, NetworkType } from '../../src' +import { FileStorage, writeLocalFile } from '../test-utils/FileStorage' + +// use chai-as-promised plugin +chai.use(chaiAsPromised) +const expect = chai.expect + +// TODO: Use AccountInfo type +const account1: any = { + accountIdentifier: 'a1', + beaconId: 'id1', + origin: { + type: Origin.P2P, + id: 'o1' + }, + address: 'tz1', + pubkey: 'pubkey1', + network: { type: NetworkType.MAINNET }, + scopes: [], + connectedAt: new Date().getTime() +} + +const account2: any = { + accountIdentifier: 'a2', + beaconId: 'id2', + origin: { + type: Origin.P2P, + id: 'o2' + }, + address: 'tz2', + pubkey: 'pubkey2', + network: { type: NetworkType.MAINNET }, + scopes: [], + connectedAt: new Date().getTime() +} + +const account3: any = { + accountIdentifier: 'a3', + beaconId: 'id3', + origin: { + type: Origin.P2P, + id: 'o3' + }, + address: 'tz3', + pubkey: 'pubkey3', + network: { type: NetworkType.MAINNET }, + scopes: [], + connectedAt: new Date().getTime() +} + +describe(`AccountManager`, () => { + let manager: AccountManager + beforeEach(async () => { + await writeLocalFile({}) + + manager = new AccountManager(new FileStorage()) + }) + it(`reads and adds accounts`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + const accountsAfter: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfter.length, 'after').to.equal(1) + }) + + it(`reads and adds multiple accounts`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + await manager.addAccount(account2) + const accountsAfter: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfter.length, 'after').to.equal(2) + }) + + it(`only adds an account once`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + await manager.addAccount(account1) + const accountsAfter: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfter.length, 'after').to.equal(1) + }) + + it(`reads one account`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + await manager.addAccount(account2) + const account = await manager.getAccount(account1.accountIdentifier) + expect(account, 'after').to.deep.include(account1) + }) + + it(`removes one account`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + await manager.addAccount(account2) + await manager.addAccount(account3) + const accountsAfter: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfter.length, 'after add').to.equal(3) + + await manager.removeAccount(account1.accountIdentifier) + const accountsAfterRemove: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfterRemove.length, 'after remove').to.equal(2) + expect(accountsAfterRemove, 'after remove, account2').to.deep.include(account2) + expect(accountsAfterRemove, 'after remove, account3').to.deep.include(account3) + }) + + it(`removes many accounts`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + await manager.addAccount(account2) + await manager.addAccount(account3) + const accountsAfter: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfter.length, 'after add').to.equal(3) + + await manager.removeAccounts([account1.accountIdentifier, account2.accountIdentifier]) + const accountsAfterRemove: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfterRemove.length, 'after remove').to.equal(1) + expect(accountsAfterRemove, 'after remove').to.deep.include(account3) + }) + + it(`removes all accounts`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + await manager.addAccount(account2) + await manager.addAccount(account3) + const accountsAfter: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfter.length, 'after add').to.equal(3) + + await manager.removeAllAccounts() + const accountsAfterRemove: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfterRemove.length, 'after remove').to.equal(0) + }) +}) diff --git a/test/test-utils/FileStorage.ts b/test/test-utils/FileStorage.ts new file mode 100644 index 000000000..07dadd27c --- /dev/null +++ b/test/test-utils/FileStorage.ts @@ -0,0 +1,72 @@ +import { readFile, writeFile } from 'fs' +import { defaultValues } from '../../src/types/storage/StorageKeyReturnDefaults' +import { Storage, StorageKey, StorageKeyReturnType } from '../..' + +const file: string = './storage.json' + +interface JsonObject { + [key: string]: unknown +} + +/* eslint-disable prefer-arrow/prefer-arrow-functions */ + +export function readLocalFile(): Promise { + return new Promise((resolve: (_: JsonObject) => void, reject: (error: unknown) => void): void => { + readFile(file, { encoding: 'utf8' }, (fileReadError: unknown, fileContent: string) => { + if (fileReadError) { + reject(fileReadError) + } + try { + const json: JsonObject = JSON.parse(fileContent) + resolve(json) + } catch (jsonParseError) { + reject(jsonParseError) + } + }) + }) +} + +export function writeLocalFile(json: JsonObject): Promise { + return new Promise((resolve: (_: void) => void): void => { + const fileContent: string = JSON.stringify(json) + writeFile(file, fileContent, { encoding: 'utf8' }, () => { + resolve() + }) + }) +} + +/** + * This can be used for development in node. + * + * DO NOT USE IN PRODUCTION + */ +export class FileStorage implements Storage { + public static async isSupported(): Promise { + return Promise.resolve(typeof global !== 'undefined') + } + + public async get(key: K): Promise { + const json: JsonObject = await readLocalFile() + + if (json[key]) { + return json[key] as StorageKeyReturnType[K] + } else { + return JSON.parse(JSON.stringify(defaultValues[key])) + } + } + + public async set(key: K, value: StorageKeyReturnType[K]): Promise { + const json: JsonObject = await readLocalFile() + + json[key] = value + + return writeLocalFile(json) + } + + public async delete(key: K): Promise { + const json: JsonObject = await readLocalFile() + json[key] = undefined + + return writeLocalFile(json) + } +}