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

Cache list account results #111

Merged
merged 3 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 8 additions & 1 deletion 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