Skip to content

Commit

Permalink
Cache list account results (#111)
Browse files Browse the repository at this point in the history
* cache the listAccounts() results similar to balance()

* minor code review improvements

* various improvements
  • Loading branch information
koresar authored Nov 30, 2023
1 parent a792a75 commit fc5900c
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 36 deletions.
7 changes: 7 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 @@ -55,6 +55,7 @@
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/chai": "^4.3.5",
"@types/luxon": "^3.3.5",
"@types/mocha": "^10.0.1",
"@types/node": "^18.16.17",
"@types/sinon": "^10.0.15",
Expand Down
50 changes: 32 additions & 18 deletions spec/balance.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint sonarjs/no-duplicate-string: off, no-prototype-builtins: off*/
import { expect } from "chai";
import { Book, syncIndexes } from "../src";
import { balanceModel, getBestSnapshot } from "../src/models/balance";
import { balanceModel, getBestBalanceSnapshot } from "../src/models/balance";
import { setTransactionSchema, transactionModel, transactionSchema } from "../src/models/transaction";
import { getTransactionSchemaTest, ITransactionTest } from "./helper/transactionSchema";

describe("balance model", function () {
describe("getBestSnapshot", () => {
describe("getBestBalanceSnapshot", () => {
it("should find snapshot", async function () {
const book = new Book("MyBook-balance-1");

Expand All @@ -15,7 +15,7 @@ describe("balance model", function () {
const balance1 = await book.balance({ account: "Assets:Receivable" });
expect(balance1).to.deep.equal({ balance: 1, notes: 1 });

const snapshot = await getBestSnapshot({ book: book.name, account: "Assets:Receivable" });
const snapshot = await getBestBalanceSnapshot({ book: book.name, account: "Assets:Receivable" });
expect(snapshot).to.have.property("balance", 1);
});

Expand Down Expand Up @@ -50,19 +50,23 @@ describe("balance model", function () {
expect(balance).to.deep.equal({ balance: 1, notes: 1 });

// balance with meta should equal 1.0
snapshot = await getBestSnapshot({ book: book.name, account: "Assets:Receivable", meta: { clientId: "12345" } });
snapshot = await getBestBalanceSnapshot({
book: book.name,
account: "Assets:Receivable",
meta: { clientId: "12345" },
});
expect(snapshot).to.have.property("balance", 1);

// there must be no balance without meta (yet)
snapshot = await getBestSnapshot({ book: book.name, account: "Assets:Receivable" });
snapshot = await getBestBalanceSnapshot({ book: book.name, account: "Assets:Receivable" });
expect(snapshot).to.be.not.ok;

// this should create a new balance
balance = await book.balance({ account: "Assets:Receivable" });
expect(balance).to.deep.equal({ balance: 3, notes: 2 });

// check if previously missing balance was created and equals to 3.0
snapshot = await getBestSnapshot({ book: book.name, account: "Assets:Receivable" });
snapshot = await getBestBalanceSnapshot({ book: book.name, account: "Assets:Receivable" });
expect(snapshot).to.have.property("balance", 3);
});

Expand All @@ -80,19 +84,27 @@ describe("balance model", function () {
expect(balance).to.deep.equal({ balance: 3, notes: 2 });

// balance without meta should equal 3.0
snapshot = await getBestSnapshot({ book: book.name, account: "Assets:Receivable" });
snapshot = await getBestBalanceSnapshot({ book: book.name, account: "Assets:Receivable" });
expect(snapshot).to.have.property("balance", 3);

// there must be no balance with meta (yet)
snapshot = await getBestSnapshot({ book: book.name, account: "Assets:Receivable", meta: { clientId: "12345" } });
snapshot = await getBestBalanceSnapshot({
book: book.name,
account: "Assets:Receivable",
meta: { clientId: "12345" },
});
expect(snapshot).to.be.not.ok;

// this should create a new balance, this time with meta
balance = await book.balance({ account: "Assets:Receivable", clientId: "12345" });
expect(balance).to.deep.equal({ balance: 1, notes: 1 });

// check if previously missing balance was created and equals to 1.0
snapshot = await getBestSnapshot({ book: book.name, account: "Assets:Receivable", meta: { clientId: "12345" } });
snapshot = await getBestBalanceSnapshot({
book: book.name,
account: "Assets:Receivable",
meta: { clientId: "12345" },
});
expect(snapshot).to.have.property("balance", 1);
});

Expand All @@ -108,7 +120,7 @@ describe("balance model", function () {
const balance1 = await book.balance({ account: "Assets:Receivable", clientId: { $in: ["12345", "67890"] } });
expect(balance1).to.deep.equal({ balance: 1, notes: 1 });

const snapshot = await getBestSnapshot({
const snapshot = await getBestBalanceSnapshot({
book: book.name,
account: "Assets:Receivable",
meta: { clientId: { $in: ["12345", "67890"] } },
Expand All @@ -117,7 +129,7 @@ describe("balance model", function () {
expect(snapshot).to.have.property("balance", 1);

// Let's make sure the snapshot is used when mongodb query language is present in the query
await balanceModel.collection.updateOne({ key: snapshot.key }, { $set: { balance: 300 } });
await balanceModel.collection.updateOne({ key: snapshot?.key }, { $set: { balance: 300 } });
const balance2 = await book.balance({ account: "Assets:Receivable", clientId: { $in: ["12345", "67890"] } });
expect(balance2).to.deep.equal({ balance: 300, notes: 1 });
});
Expand All @@ -139,8 +151,10 @@ describe("balance model", function () {
// We need to change the order of transactions in the database.
// The first inserted doc must have the largest _id for this unit test.
// Copying it the first transaction to the end and remove it.
const t1Object = t1.toObject();
await t1.deleteOne(); // we have to remove BEFORE creating the clone because otherwise MongoDB sees the stale (removed) doc!!!
const t1Object = t1?.toObject();
await t1?.deleteOne(); // we have to remove BEFORE creating the clone because otherwise MongoDB sees the stale (removed) doc!!!
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete t1Object._id;
await transactionModel.create(t1Object);

Expand All @@ -149,7 +163,7 @@ describe("balance model", function () {
});
});

describe("getBestSnapshot with custom schema", () => {
describe("getBestBalanceSnapshot with custom schema", () => {
let book: Book<ITransactionTest>;

before(async function () {
Expand Down Expand Up @@ -189,7 +203,7 @@ describe("balance model", function () {
expect(res.results[2].meta).to.have.property("otherMeta");
expect(res.results[2].meta).to.not.have.property("clientId");

const snapshot = await getBestSnapshot({ book: book.name, account });
const snapshot = await getBestBalanceSnapshot({ book: book.name, account });
expect(snapshot).to.have.property("balance", 3);
});

Expand All @@ -206,7 +220,7 @@ describe("balance model", function () {
expect(res.results[1].meta).to.have.property("otherMeta");
expect(res.results[1].meta).to.not.have.property("clientId");

const snapshot = await getBestSnapshot({ book: book.name, account, clientId });
const snapshot = await getBestBalanceSnapshot({ book: book.name, account, clientId });
expect(snapshot).to.have.property("balance", 2);
});

Expand All @@ -224,10 +238,10 @@ describe("balance model", function () {
expect(res.results[0].meta).to.have.property("otherMeta");
expect(res.results[0].meta).to.not.have.property("clientId");

const snapshot1 = await getBestSnapshot({ book: book.name, account, clientId, meta: { otherMeta } });
const snapshot1 = await getBestBalanceSnapshot({ book: book.name, account, clientId, meta: { otherMeta } });
expect(snapshot1).to.have.property("balance", 1);

const snapshot2 = await getBestSnapshot({ book: book.name, account, clientId, otherMeta });
const snapshot2 = await getBestBalanceSnapshot({ book: book.name, account, clientId, otherMeta });
expect(snapshot2).to.have.property("balance", 1);
});
});
Expand Down
90 changes: 90 additions & 0 deletions spec/book.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,8 @@ describe("book", function () {

before(async () => {
await book.entry("Test Entry").debit("Assets:Receivable", 700).credit("Income:Rent", 700).commit();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
journal = await book
.entry("Test Entry")
.debit("Assets:Receivable", 500, { clientId: "12345" })
Expand Down Expand Up @@ -606,6 +608,94 @@ describe("book", function () {
expect(accounts).to.have.lengthOf(4);
expect(accounts).to.have.members(["Assets", "Income", "Income:Rent", "Income:Rent:Taxable"]);
});

async function addBalance(book: Book, suffix = "") {
await book
.entry("Test Entry")
.debit("Assets:Receivable" + suffix, 700, { clientId: "67890", otherProp: 1 })
.credit("Income:Rent" + suffix, 700)
.commit();
await book
.entry("Test Entry")
.debit("Assets:Receivable" + suffix, 500, { clientId: "12345", otherProp: 1 })
.credit("Income:Rent" + suffix, 500)
.commit();
}

it("should reuse the snapshot listAccounts", async () => {
const book = new Book("MyBook-listAccounts-snapshot");
await addBalance(book);

await book.listAccounts();

let snapshots = await balanceModel.find({ book: book.name });
expect(snapshots).to.have.length(1);
expect(snapshots[0].meta.accounts).to.deep.equal(["Assets", "Assets:Receivable", "Income", "Income:Rent"]);

snapshots[0].meta.accounts = ["new-list"];
await snapshots[0].save();

await book.listAccounts();
snapshots = await balanceModel.find({ book: book.name });

expect(snapshots).to.have.length(1);
expect(snapshots[0].meta.accounts).to.deep.equal(["Assets", "Assets:Receivable", "Income", "Income:Rent"]);
});

it("should create only one snapshot document", async () => {
const book = new Book("MyBook-listAccounts-snapshot-count");
await addBalance(book);

await book.listAccounts();
await book.listAccounts();
await book.listAccounts();

const snapshots = await balanceModel.find({ book: book.name });
expect(snapshots).to.have.length(1);
expect(snapshots[0].meta.accounts).to.deep.equal(["Assets", "Assets:Receivable", "Income", "Income:Rent"]);
});

it("should create periodic balance snapshot document", async () => {
const howOften = 50; // milliseconds
const book = new Book("MyBook-listAccounts-snapshot-periodic", { balanceSnapshotSec: howOften / 1000 });

await addBalance(book);

await book.listAccounts();
// Should be one snapshot.
let snapshots = await balanceModel.find({ book: book.name });
expect(snapshots.length).to.equal(1);
expect(snapshots[0].meta.accounts).to.deep.equal(["Assets", "Assets:Receivable", "Income", "Income:Rent"]);

await delay(howOften + 1); // wait long enough to create a second periodic snapshot

await addBalance(book, "2");
await book.listAccounts();
await delay(10); // wait until the full listAccounts snapshot is recalculated in the background

// Should be two snapshots now.
snapshots = await balanceModel.find({ book: book.name });
expect(snapshots.length).to.equal(2);
expect(snapshots[0].meta.accounts).to.deep.equal(["Assets", "Assets:Receivable", "Income", "Income:Rent"]);
expect(snapshots[1].meta.accounts).to.deep.equal([
"Assets",
"Assets:Receivable",
"Assets:Receivable2",
"Income",
"Income:Rent",
"Income:Rent2",
]);
});

it("should not do listAccounts snapshots if turned off", async () => {
const book = new Book("MyBook-balance-listAccounts-off", { balanceSnapshotSec: 0 });
await addBalance(book);

await book.listAccounts();

const snapshots = await balanceModel.find({ book: book.name });
expect(snapshots).to.have.length(0);
});
});

describe("ledger", () => {
Expand Down
Loading

0 comments on commit fc5900c

Please sign in to comment.