acebase/spec/transactions.spec.js
2021-06-04 13:21:59 +02:00

249 lines
No EOL
7.6 KiB
JavaScript

/// <reference types="@types/jasmine" />
const { createTempDB } = require('./tempdb');
describe('transactions', () => {
it('can be canceled', async () => {
const { db, removeDB } = await createTempDB();
const ref = db.ref('accounts/my_account');
// Try returning nothing to cancel transaction
await ref.transaction(snap => {
// Return nothing
});
let snap = await ref.get();
expect(snap.exists()).toBeFalse();
// Try with a promise that resolves without a value
await ref.transaction(snap => {
return Promise.resolve();
});
snap = await ref.get();
expect(snap.exists()).toBeFalse();
// Same as above using async
await ref.transaction(async snap => {
// Return nothing
});
snap = await ref.get();
expect(snap.exists()).toBeFalse();
await removeDB();
});
it('can return promises', async () => {
const { db, removeDB } = await createTempDB();
const ref = db.ref('accounts/my_account');
await ref.transaction(async snap => {
await new Promise(resolve => setTimeout(resolve, 0));
return { balance: 150 };
});
const snap = await ref.get();
const account = snap.val();
expect(account.balance).toEqual(150);
await removeDB();
});
it('removes nodes when returning null', async () => {
const { db, removeDB } = await createTempDB();
const ref = db.ref('accounts/my_account');
// Create node
await ref.set({ balance: 500 });
await ref.transaction(snap => {
const account = snap.val();
expect(account.balance).toEqual(500);
return null;
});
let snap = await ref.get();
expect(snap.exists()).toBeFalse();
// Create node again
await ref.set({ balance: 1000 });
// With promise
await ref.transaction(snap => {
const account = snap.val();
expect(account.balance).toEqual(1000);
return Promise.resolve(null);
});
snap = await ref.get();
expect(snap.exists()).toBeFalse();
// Create node again
await ref.set({ balance: 1500 });
// With async
await ref.transaction(async snap => {
const account = snap.val();
expect(account.balance).toEqual(1500);
return null;
});
snap = await ref.get();
expect(snap.exists()).toBeFalse();
await removeDB();
});
it('can run in serial', async () => {
// Create temp db
const { db, removeDB } = await createTempDB();
db.ref("accounts").on("child_changed").subscribe(snap => {
console.log(`Account ${snap.key} changed, balance = ${snap.val().balance}`);
});
// Test serial transaction (1 followed by another when done)
const ref = db.ref("accounts/my_account");
// Initialize account value
await ref.set({ balance: 50 });
// Perform transaction on "account" node
await ref.transaction(snap => {
let account = snap.val();
expect(account.balance).toEqual(50);
account.balance -= 15;
return account;
});
// Perform transaction on "account/balance" node
const childRef = await ref.child("balance").transaction(snap => {
let balance = snap.val();
expect(balance).toEqual(35);
balance -= 10;
return balance;
});
// Assert the resolved ref points to the child node
expect(childRef.path).toEqual(ref.child("balance").path);
// Get "account" value through childRef.parent
const snap = await childRef.parent.get();
let balance = snap.val().balance;
expect(balance).toEqual(25);
// console.log(`Account balance was succesfully decreased to ${balance}`);
// Remove temp db
await removeDB();
});
it('can run in parallel', async () => {
// Test 3 parallel transactions (executed at the same time)
const { db, removeDB } = await createTempDB();
const startBalance = 500;
let expectedEndBalance = startBalance;
const withdraw = (snapshot, amount) => {
let account = snapshot.val();
let currentBalance = account.balance;
account.balance -= amount;
if (account.balance < 0) {
console.error(`Insufficient funds to withdraw ${amount} from ${ref.key}, balance = ${currentBalance}`);
return; // cancels this transaction
}
else {
expectedEndBalance -= amount;
console.log(`Withdrew ${amount} from ${ref.key}, old balance = ${currentBalance}, new balance = ${account.balance}`);
}
return account;
};
const ref = db.ref("accounts/my_account");
// Initialize account balance
await ref.set({
balance: startBalance
});
// First transaction:
let t1 = ref.transaction(snap => {
return withdraw(snap, 100);
});
// Second transaction:
let t2 = ref.transaction(snap => {
return withdraw(snap, 275);
});
// Third transaction:
let t3 = ref.transaction(snap => {
return withdraw(snap, 450);
});
// Wait until all transactions to be processed
await Promise.all([t1, t2, t3]);
// console.log(`All parallel transactions processed`);
// Check final balance
const snap = await ref.get();
const balance = snap.val().balance;
expect(balance).toEqual(expectedEndBalance);
// console.log(`Final balance = ${snap.val().balance}`);
await removeDB();
});
it('handles errors', async () => {
const { db, removeDB } = await createTempDB();
const ref = db.ref("accounts/my_account");
await ref.set({
balance: 50
});
// Test transaction throwing an error
let promise = ref.transaction(snap => {
throw new Error('Not enough money!');
});
await expectAsync(promise).toBeRejected();
// Test transaction returning rejected promise
promise = ref.child("balance").transaction(snap => {
return Promise.reject(new Error(`Not enough money!`));
});
await expectAsync(promise).toBeRejected();
// Test transaction with async error
promise = ref.child("balance").transaction(async snap => {
throw new Error(`Not enough money!`);
});
await expectAsync(promise).toBeRejected();
// Nothing should have changed, check that
const snap = await ref.get();
let balance = snap.val().balance;
expect(balance).toEqual(50);
await removeDB();
});
it('handles concurrency', async () => {
// Similar to the parallel test
// use 100 concurrent transactions to update a single value
const { db, removeDB } = await createTempDB();
const ref = await db.ref("value").set(0);
const promises = [];
for (let i = 0; i < 100; i++) {
let p = ref.transaction(snap => {
return snap.val() + 1;
});
promises.push(p);
}
await Promise.all(promises);
const snap = await ref.get();
expect(snap.val()).toEqual(100);
await removeDB();
})
})