Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add database tests #285

Merged
merged 3 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^4.2.2",
"eslint": "^8.31.0",
"fake-indexeddb": "^5.0.1",
"file-loader": "^6.2.0",
"gh-pages": "^5.0.0",
"happy-dom": "^12.10.3",
Expand Down
22 changes: 14 additions & 8 deletions scripts/database.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { openDB, IDBPDatabase } from 'idb';

Check warning on line 1 in scripts/database.js

View workflow job for this annotation

GitHub Actions / Run linters

'IDBPDatabase' is defined but never used
import Masternode from './masternode.js';
import { Settings } from './settings.js';
import { cChainParams } from './chain_params.js';
Expand Down Expand Up @@ -100,13 +100,13 @@
}
/**
* Removes a Promo Code from the Promo management system
* @param {string} promo - the promo code to remove
* @param {string} promoCode - the promo code to remove
*/
async removePromo(promo) {
async removePromo(promoCode) {
const store = this.#db
.transaction('promos', 'readwrite')
.objectStore('promos');
await store.delete(promo);
await store.delete(promoCode);
}

/**
Expand All @@ -127,7 +127,9 @@
'warning',
'<b>Account Creation Error</b><br>Logs were dumped in your Browser Console<br>Please submit these privately to PIVX Labs Developers!'
);
return false;
throw new Error(
'addAccount was called with with an invalid account'
);
}

// Create an empty DB Account
Expand Down Expand Up @@ -157,7 +159,7 @@

// Check this account isn't already added (by pubkey once multi-account)
if (await store.get('account'))
return console.error(
throw new Error(
'DB: Ran addAccount() when account already exists!'
);

Expand Down Expand Up @@ -190,7 +192,9 @@
'warning',
'<b>DB Update Error</b><br>Your wallet is safe, logs were dumped in your Browser Console<br>Please submit these privately to PIVX Labs Developers!'
);
return false;
throw new Error(
'addAccount was called with with an invalid account'
);
}

// Fetch the DB account
Expand All @@ -208,7 +212,9 @@
'warning',
'<b>DB Update Error</b><br>Logs were dumped in your Browser Console<br>Please submit these privately to PIVX Labs Developers!'
);
return false;
throw new Error(
"updateAccount was called, but the account doesn't exist"
);
}

// We'll overlay the `account` keys atop the `DB Account` keys:
Expand Down Expand Up @@ -245,7 +251,7 @@
* @param {Object} o
* @param {String} o.publicKey - Public key associated to the account.
*/
async removeAccount({ publicKey }) {

Check warning on line 254 in scripts/database.js

View workflow job for this annotation

GitHub Actions / Run linters

'publicKey' is defined but never used. Allowed unused args must match /^_/u
const store = this.#db
.transaction('accounts', 'readwrite')
.objectStore('accounts');
Expand Down Expand Up @@ -483,7 +489,7 @@
});
database.#db = db;
if (migrate) {
database.#migrateLocalStorage();
await database.#migrateLocalStorage();
}
return database;
}
Expand Down
226 changes: 226 additions & 0 deletions tests/unit/database.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import 'fake-indexeddb/auto';
import { PromoWallet } from '../../scripts/promos.js';
import { it, describe, vi, expect } from 'vitest';
import { Database } from '../../scripts/database.js';
import { Account } from '../../scripts/accounts';
import * as misc from '../../scripts/misc.js';
import { Settings } from '../../scripts/settings';
import Masternode from '../../scripts/masternode';
describe('database tests', () => {
beforeAll(() => {
// Mock createAlert
vi.spyOn(misc, 'createAlert').mockImplementation(vi.fn());
vi.stubGlobal(global.console, 'error');
return () => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
};
});
beforeEach(async () => {
// Reset indexedDB before each test
vi.stubGlobal('indexedDB', new IDBFactory());
return vi.unstubAllGlobals;
});
it('stores account correctly', async () => {
const db = await Database.create('test');
const account = new Account({
publicKey: 'test1',
coldAddress: 'very cold',
});
await db.addAccount(account);
expect(await db.getAccount()).toStrictEqual(account);
await db.updateAccount(
new Account({
encWif: 'newWIF!',
localProposals: ['prop1', 'prop2'],
})
);
expect((await db.getAccount()).encWif).toBe('newWIF!');
expect((await db.getAccount()).publicKey).toBe('test1');
expect((await db.getAccount()).coldAddress).toBe('very cold');
expect((await db.getAccount()).localProposals).toStrictEqual([
'prop1',
'prop2',
]);

// Setting localProposals as empty doesn't overwrite the array
await db.updateAccount(
new Account({
encWif: 'newWIF2!',
localProposals: [],
})
);
expect((await db.getAccount()).localProposals).toStrictEqual([
'prop1',
'prop2',
]);

// Unless `allowDeletion` is set to true
await db.updateAccount(
new Account({
encWif: 'newWIF2!',
localProposals: [],
}),
true
);
expect((await db.getAccount()).localProposals).toHaveLength(0);

await db.removeAccount({ publicKey: 'test1' });

expect(await db.getAccount()).toBeNull();
});

it.todo('stores transaction correctly', () => {
// To avoid conflicts, I will implement this after #284
});

it('stores masternodes correctly', async () => {
const db = await Database.create('test');
// Masternode should be null by default
expect(await db.getMasternode()).toBe(null);
let masternode = new Masternode({
collateralTxId: 'mntxid',
});
await db.addMasternode(masternode);
expect(await db.getMasternode()).toStrictEqual(masternode);
masternode = new Masternode({
collateralTxId: 'mntxid2',
});
// Subsequent calls to `addMasternode` should overwrite it.
await db.addMasternode(masternode);
expect(await db.getMasternode()).toStrictEqual(masternode);
// Check that it removes mn correectly
await db.removeMasternode();
expect(await db.getMasternode()).toBe(null);
});

it('stores promos correctly', async () => {
const testPromos = new Array(50).fill(0).map(
(_, i) =>
new PromoWallet({
code: `${i}`,
})
);
const db = await Database.create('test');
// It starts with no promos
expect(await db.getAllPromos()).toHaveLength(0);

await db.addPromo(testPromos[0]);
expect(await db.getAllPromos()).toStrictEqual([testPromos[0]]);

// If we add the same promo twice, it should not duplicate it
await db.addPromo(testPromos[0]);
expect(await db.getAllPromos()).toStrictEqual([testPromos[0]]);

// Removes correctly
await db.removePromo(testPromos[0].code);
expect(await db.getAllPromos()).toHaveLength(0);

for (const promo of testPromos) {
await db.addPromo(promo);
}
expect(
(await db.getAllPromos()).sort(
(a, b) => parseInt(a.code) - parseInt(b.code)
)
).toStrictEqual(testPromos);
await db.removePromo('23');
expect(
(await db.getAllPromos()).sort(
(a, b) => parseInt(a.code) - parseInt(b.code)
)
).toStrictEqual(testPromos.filter((p) => p.code != '23'));
});

it('stores settings correctly', async () => {
const db = await Database.create('test');
const settings = new Settings({
explorer: 'duddino.com',
node: 'pivx.com',
});
// Settings should be left as default at the beginning
expect(await db.getSettings()).toStrictEqual(new Settings());
await db.setSettings(settings);
expect(await db.getSettings()).toStrictEqual(settings);
// Test that overwrite works as expected
await db.setSettings({
node: 'pivx.org',
});
expect(await db.getSettings()).toStrictEqual(
new Settings({
explorer: 'duddino.com',
node: 'pivx.org',
})
);
});

it('throws when calling addAccount twice', async () => {
const db = await Database.create('test');
const account = new Account();
db.addAccount(account);
expect(() => db.addAccount(account)).rejects.toThrow(
/account already exists/i
);
});
it('throws when called with an invalid account', async () => {
const db = await Database.create('test');
expect(() => db.addAccount({ publicKey: 'jaeir' })).rejects.toThrow(
/invalid account/
);
expect(() => db.updateAccount({ publicKey: 'jaeir' })).rejects.toThrow(
/invalid account/
);
});
it("throws when updating an account that doesn't exist", async () => {
const db = await Database.create('test');
expect(() => db.updateAccount(new Account())).rejects.toThrow(
/account doesn't exist/
);
});

it('migrates from local storage correctly', async () => {
vi.stubGlobal('localStorage', {
explorer: 'duddino.com',
translation: 'DE',
encwif: 'ENCRYPTED_WIF',
publicKey: 'PUB_KEY',
masternode: JSON.stringify(
new Masternode({ collateralTxId: 'mntxid' })
),
});
const db = await Database.create('test');
expect(await db.getAccount()).toStrictEqual(
new Account({
publicKey: 'PUB_KEY',
encWif: 'ENCRYPTED_WIF',
})
);
expect(await db.getSettings()).toStrictEqual(
new Settings({
explorer: 'duddino.com',
translation: 'DE',
})
);
expect(await db.getMasternode()).toStrictEqual(
new Masternode({ collateralTxId: 'mntxid' })
);

vi.unstubAllGlobals();
});

it('is isolated between different instances', async () => {
const db = await Database.create('test');
const db2 = await Database.create('test2');
// Initially, both accounts are null
expect(await db.getAccount()).toBe(null);
expect(await db2.getAccount()).toBe(null);
const account = new Account({
publicKey: 'test1',
});
// Let's add an account to the first db
await db.addAccount(account);
// First DB has the account, the second one is undefined
expect((await db.getAccount())?.publicKey).toBe('test1');
expect((await db2.getAccount())?.publicKey).toBeUndefined();
});
});
Loading