test/shared/accounting/store/batching.test.ts
Rama main · réplica de lectura del repositorio original.
/** The checks a post runs before writing are read in one go, not one by one. */
import { assertStringIncludes } from "@std/assert";
import { expect } from "@std/expect";
import { describe, it as test } from "@std/testing/bdd";
import { transfersByEventGroup } from "#accounting/queries.ts";
import { postTransferGroups, postTransfers } from "#accounting/store.ts";
import { account } from "#shared/ledger/account.ts";
import type { TransferInput } from "#shared/ledger/types.ts";
import { saleAndPayment, tx, useTransactionalDb } from "#test-utils/ledger.ts";
import { recordQueries } from "#test-utils/record-queries.ts";
/** Every recorded statement that reads the ledger. `recordQueries` writes a
* whole batch as one entry, so this counts round trips, not statements. */
const ledgerReads = (seen: readonly string[]): string[] =>
seen.filter((sql) => sql.includes("FROM transfers"));
/** The ledger round trips one piece of work takes. */
const readsDuring = async (work: () => Promise<unknown>): Promise<string[]> => {
const seen: string[] = [];
const restore = recordQueries(seen);
try {
await work();
} finally {
restore();
}
return ledgerReads(seen);
};
/** A leg voiding `originalId`, which makes the post check the original too. */
const reversalOf = (originalId: number): TransferInput[] => [
tx({
destination: account("attendee", 1),
eventGroup: "evt-void",
reference: "void",
reversesId: originalId,
source: account("revenue", 1),
}),
];
/** Store one sale on its own and hand back the id a reversal can point at. */
const storedSaleId = async (): Promise<number> => {
await postTransfers([tx({ eventGroup: "evt-1", reference: "sale" })]);
const [sale] = await transfersByEventGroup("evt-1");
if (sale === undefined) throw new Error("The stored sale was not read back");
return sale.id;
};
describe("db > accounting > store > snapshot batching", () => {
useTransactionalDb();
test("checks a fresh event against the ledger in one round trip", async () => {
const reads = await readsDuring(() =>
postTransferGroups([saleAndPayment()]),
);
expect(reads).toHaveLength(1);
});
test("still takes one round trip when a reversal adds a third check", async () => {
const originalId = await storedSaleId();
const reads = await readsDuring(() =>
postTransferGroups([reversalOf(originalId)]),
);
expect(reads).toHaveLength(1);
});
test("asks all three checks within the one round trip it takes", async () => {
const originalId = await storedSaleId();
const [snapshot] = await readsDuring(() =>
postTransferGroups([reversalOf(originalId)]),
);
assertStringIncludes(snapshot ?? "", "event_group IN");
assertStringIncludes(snapshot ?? "", "reference IN");
assertStringIncludes(snapshot ?? "", "id IN");
});
test("skips the round trip entirely when there is nothing to check", async () => {
const reads = await readsDuring(() => postTransferGroups([[], []]));
expect(reads).toEqual([]);
});
});