mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: coverage campaign and mutation testing across five packages (#628)
Coverage 83.6 to 87.36% lines, 81.63 to 84.14% branches. Mutation testing across five packages: image-engine 85, media-engine 92, doc-engine 87, shared+enterprise 86, apps/api security and jobs slice. Runs all five lanes weekly. Fixes the silently-broken mutation CI (babel pin), a redact-pdf envelope-shape test bug, an untested enterprise license valid-signature path, and an audit test that only exercised a hand-copied reproduction. Test and config only, no product code changes beyond the babel pin and one test-only oidc export. Full suite: 16,712 pass, 0 fail.
This commit is contained in:
@@ -5,6 +5,9 @@ const selectMock = vi.hoisted(() => vi.fn());
|
||||
const deleteMock = vi.hoisted(() => vi.fn());
|
||||
const upsertSettingMock = vi.hoisted(() => vi.fn());
|
||||
const mkdirMock = vi.hoisted(() => vi.fn());
|
||||
const statMock = vi.hoisted(() => vi.fn());
|
||||
const pipelineMock = vi.hoisted(() => vi.fn());
|
||||
const createWriteStreamMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
function queryChain<T>(result: T) {
|
||||
const chain = {
|
||||
@@ -14,6 +17,17 @@ function queryChain<T>(result: T) {
|
||||
return chain;
|
||||
}
|
||||
|
||||
function makeLogger() {
|
||||
return {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
trace: vi.fn(),
|
||||
fatal: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
async function loadAuditArchive() {
|
||||
vi.resetModules();
|
||||
isFeatureEnabledMock.mockReset();
|
||||
@@ -21,18 +35,28 @@ async function loadAuditArchive() {
|
||||
deleteMock.mockReset();
|
||||
upsertSettingMock.mockReset();
|
||||
mkdirMock.mockReset();
|
||||
statMock.mockReset();
|
||||
pipelineMock.mockReset();
|
||||
createWriteStreamMock.mockReset();
|
||||
|
||||
// Sensible defaults for the write path: mkdir/pipeline resolve, stat resolves.
|
||||
mkdirMock.mockResolvedValue(undefined);
|
||||
pipelineMock.mockResolvedValue(undefined);
|
||||
statMock.mockResolvedValue({ size: 123 });
|
||||
createWriteStreamMock.mockReturnValue({});
|
||||
upsertSettingMock.mockResolvedValue(undefined);
|
||||
|
||||
vi.doMock("node:fs/promises", () => ({
|
||||
mkdir: mkdirMock,
|
||||
stat: vi.fn(),
|
||||
stat: statMock,
|
||||
}));
|
||||
|
||||
vi.doMock("node:fs", () => ({
|
||||
createWriteStream: vi.fn(),
|
||||
createWriteStream: createWriteStreamMock,
|
||||
}));
|
||||
|
||||
vi.doMock("node:stream/promises", () => ({
|
||||
pipeline: vi.fn(),
|
||||
pipeline: pipelineMock,
|
||||
}));
|
||||
|
||||
vi.doMock("drizzle-orm", () => ({
|
||||
@@ -86,6 +110,34 @@ describe("audit archive job behavior", () => {
|
||||
expect(upsertSettingMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats a thrown enterprise check as unlicensed and returns early", async () => {
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
isFeatureEnabledMock.mockImplementation(() => {
|
||||
throw new Error("license subsystem exploded");
|
||||
});
|
||||
|
||||
await runAuditArchive();
|
||||
|
||||
// The catch keeps featureEnabled false, so no settings are ever read.
|
||||
expect(selectMock).not.toHaveBeenCalled();
|
||||
expect(upsertSettingMock).not.toHaveBeenCalled();
|
||||
expect(deleteMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns when the auditArchiveMonths setting is absent (null value)", async () => {
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
// readSettingValue returns null when there is no row.
|
||||
selectMock.mockReturnValueOnce(queryChain([]));
|
||||
|
||||
await runAuditArchive();
|
||||
|
||||
// Only the archiveMonths read happened; state was never touched.
|
||||
expect(selectMock).toHaveBeenCalledTimes(1);
|
||||
expect(upsertSettingMock).not.toHaveBeenCalled();
|
||||
expect(mkdirMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns when archive months is missing or disabled", async () => {
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
@@ -97,6 +149,17 @@ describe("audit archive job behavior", () => {
|
||||
expect(upsertSettingMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns when archive months parses to a negative number", async () => {
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
selectMock.mockReturnValueOnce(queryChain([{ value: "-3" }]));
|
||||
|
||||
await runAuditArchive();
|
||||
|
||||
expect(mkdirMock).not.toHaveBeenCalled();
|
||||
expect(upsertSettingMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears archival state when there are no rows older than the boundary", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue(undefined);
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
@@ -107,7 +170,8 @@ describe("audit archive job behavior", () => {
|
||||
.mockReturnValueOnce(queryChain([]))
|
||||
.mockReturnValueOnce(queryChain([]));
|
||||
|
||||
await runAuditArchive();
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
expect(upsertSettingMock).toHaveBeenCalledWith(
|
||||
"audit_archival_state",
|
||||
@@ -115,5 +179,320 @@ describe("audit archive job behavior", () => {
|
||||
);
|
||||
expect(mkdirMock).toHaveBeenCalledWith("/data/audit-archives", { recursive: true });
|
||||
expect(deleteWhere).toHaveBeenCalled();
|
||||
expect(log.info).toHaveBeenCalledWith("No audit rows older than boundary, nothing to archive");
|
||||
});
|
||||
|
||||
it("runs a full fresh archive: export, verify, purge, and complete", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue({ rowCount: 2 });
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
selectMock
|
||||
// auditArchiveMonths
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
// STATE_KEY (fresh, no existing state)
|
||||
.mockReturnValueOnce(queryChain([]))
|
||||
// audit rows older than boundary
|
||||
.mockReturnValueOnce(queryChain([{ id: "a1" }, { id: "a2" }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
// Wrote the archive through the gzip pipeline.
|
||||
expect(mkdirMock).toHaveBeenCalledWith("/data/audit-archives", { recursive: true });
|
||||
expect(createWriteStreamMock).toHaveBeenCalledTimes(1);
|
||||
expect(pipelineMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Progressed through EXPORTED, PURGING, COMPLETE state persistence.
|
||||
const upsertStates = upsertSettingMock.mock.calls.map((c) => c[1] as string);
|
||||
expect(upsertStates.some((s) => s.includes('"state":"EXPORTING"'))).toBe(true);
|
||||
expect(upsertStates.some((s) => s.includes('"state":"EXPORTED"'))).toBe(true);
|
||||
expect(upsertStates.some((s) => s.includes('"state":"PURGING"'))).toBe(true);
|
||||
expect(upsertStates.some((s) => s.includes('"state":"COMPLETE"'))).toBe(true);
|
||||
|
||||
// Purge used the reported rowCount from the delete result.
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ purgedRows: 2 }),
|
||||
"Purged archived audit rows",
|
||||
);
|
||||
|
||||
// Verify + complete log lines fired.
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ rowCount: 2 }),
|
||||
"Archive verified",
|
||||
);
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ rowCount: 2 }),
|
||||
"Audit archival complete",
|
||||
);
|
||||
|
||||
// Final COMPLETE step deletes the state key.
|
||||
expect(deleteMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the recorded rowCount when the purge result omits rowCount", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue({}); // no rowCount field
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([]))
|
||||
.mockReturnValueOnce(queryChain([{ id: "a1" }, { id: "a2" }, { id: "a3" }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ purgedRows: 3 }),
|
||||
"Purged archived audit rows",
|
||||
);
|
||||
});
|
||||
|
||||
it("resumes from a persisted EXPORTED state and completes purge", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue({ rowCount: 5 });
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
const persisted = JSON.stringify({
|
||||
state: "EXPORTED",
|
||||
dateBoundary: "2026-01-01T00:00:00.000Z",
|
||||
outputPath: "/data/audit-archives/audit-archive-2026-01-01T00-00-00-000Z.ndjson.gz",
|
||||
rowCount: 5,
|
||||
checksum: "abc123",
|
||||
});
|
||||
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([{ value: persisted }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
// Resumed rather than re-exporting.
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ state: "EXPORTED" }),
|
||||
"Resuming audit archival from persisted state",
|
||||
);
|
||||
// Did NOT re-run the export path (no mkdir / pipeline / new file).
|
||||
expect(mkdirMock).not.toHaveBeenCalled();
|
||||
expect(pipelineMock).not.toHaveBeenCalled();
|
||||
// Verified the existing archive file.
|
||||
expect(statMock).toHaveBeenCalledWith(
|
||||
"/data/audit-archives/audit-archive-2026-01-01T00-00-00-000Z.ndjson.gz",
|
||||
);
|
||||
// Purged and completed.
|
||||
expect(deleteWhere).toHaveBeenCalled();
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ purgedRows: 5 }),
|
||||
"Purged archived audit rows",
|
||||
);
|
||||
});
|
||||
|
||||
it("aborts when the archive file is missing at the EXPORTED verify step", async () => {
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
const deleteWhere = vi.fn().mockResolvedValue(undefined);
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
statMock.mockRejectedValue(new Error("ENOENT"));
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
const persisted = JSON.stringify({
|
||||
state: "EXPORTED",
|
||||
dateBoundary: "2026-01-01T00:00:00.000Z",
|
||||
outputPath: "/data/audit-archives/missing.ndjson.gz",
|
||||
rowCount: 5,
|
||||
checksum: "abc123",
|
||||
});
|
||||
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([{ value: persisted }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ path: "/data/audit-archives/missing.ndjson.gz" }),
|
||||
"Archive file missing after export, aborting",
|
||||
);
|
||||
// State key was cleared via db.delete (deleteSetting on the settings table).
|
||||
expect(deleteMock).toHaveBeenCalledWith(expect.objectContaining({ key: "settings.key" }));
|
||||
// Aborted before purging: never deleted from the audit log table.
|
||||
expect(deleteMock).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ createdAt: "auditLog.createdAt" }),
|
||||
);
|
||||
// Never advanced to PURGING.
|
||||
const upsertStates = upsertSettingMock.mock.calls.map((c) => c[1] as string);
|
||||
expect(upsertStates.some((s) => s.includes('"state":"PURGING"'))).toBe(false);
|
||||
});
|
||||
|
||||
it("aborts when a resumed EXPORTED state has no checksum", async () => {
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
const deleteWhere = vi.fn().mockResolvedValue(undefined);
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
const persisted = JSON.stringify({
|
||||
state: "EXPORTED",
|
||||
dateBoundary: "2026-01-01T00:00:00.000Z",
|
||||
outputPath: "/data/audit-archives/present.ndjson.gz",
|
||||
rowCount: 5,
|
||||
checksum: "", // invalid: missing checksum
|
||||
});
|
||||
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([{ value: persisted }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
expect(statMock).toHaveBeenCalled();
|
||||
expect(log.error).toHaveBeenCalledWith("Invalid archival state: missing checksum or rowCount");
|
||||
// Aborted before purge: state key deleted, audit table untouched.
|
||||
expect(deleteMock).toHaveBeenCalledWith(expect.objectContaining({ key: "settings.key" }));
|
||||
expect(deleteMock).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ createdAt: "auditLog.createdAt" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("aborts when a resumed EXPORTED state has a non-positive rowCount", async () => {
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
const deleteWhere = vi.fn().mockResolvedValue(undefined);
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
const persisted = JSON.stringify({
|
||||
state: "EXPORTED",
|
||||
dateBoundary: "2026-01-01T00:00:00.000Z",
|
||||
outputPath: "/data/audit-archives/present.ndjson.gz",
|
||||
rowCount: 0, // invalid
|
||||
checksum: "abc123",
|
||||
});
|
||||
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([{ value: persisted }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
expect(log.error).toHaveBeenCalledWith("Invalid archival state: missing checksum or rowCount");
|
||||
// Aborted before purge: audit table untouched.
|
||||
expect(deleteMock).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ createdAt: "auditLog.createdAt" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("resumes from a persisted PENDING state and progresses forward", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue({ rowCount: 1 });
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
const persisted = JSON.stringify({
|
||||
state: "PENDING",
|
||||
dateBoundary: "2026-01-01T00:00:00.000Z",
|
||||
outputPath: "",
|
||||
rowCount: 0,
|
||||
checksum: "",
|
||||
});
|
||||
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([{ value: persisted }]))
|
||||
// rows to export
|
||||
.mockReturnValueOnce(queryChain([{ id: "a1" }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ state: "PENDING" }),
|
||||
"Resuming audit archival from persisted state",
|
||||
);
|
||||
// PENDING flips to EXPORTING and the full pipeline runs.
|
||||
expect(pipelineMock).toHaveBeenCalledTimes(1);
|
||||
expect(deleteWhere).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts fresh when the persisted state JSON is corrupt", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue({ rowCount: 1 });
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
// corrupt state -> JSON.parse throws -> deleteSetting + freshRun
|
||||
.mockReturnValueOnce(queryChain([{ value: "{not valid json" }]))
|
||||
.mockReturnValueOnce(queryChain([{ id: "a1" }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
// Corrupt state key was deleted before starting fresh.
|
||||
expect(deleteMock).toHaveBeenCalled();
|
||||
// A fresh run starts at PENDING then flips to EXPORTING.
|
||||
const upsertStates = upsertSettingMock.mock.calls.map((c) => c[1] as string);
|
||||
expect(upsertStates.some((s) => s.includes('"state":"EXPORTING"'))).toBe(true);
|
||||
// Never logged a resume because parse failed.
|
||||
expect(log.info).not.toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
"Resuming audit archival from persisted state",
|
||||
);
|
||||
});
|
||||
|
||||
it("resumes directly from a persisted PURGING state and completes", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue({ rowCount: 4 });
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
const persisted = JSON.stringify({
|
||||
state: "PURGING",
|
||||
dateBoundary: "2026-01-01T00:00:00.000Z",
|
||||
outputPath: "/data/audit-archives/present.ndjson.gz",
|
||||
rowCount: 4,
|
||||
checksum: "abc123",
|
||||
});
|
||||
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([{ value: persisted }]));
|
||||
|
||||
const log = makeLogger();
|
||||
await runAuditArchive(log as never);
|
||||
|
||||
// Went straight to purge, skipping export + verify.
|
||||
expect(pipelineMock).not.toHaveBeenCalled();
|
||||
expect(statMock).not.toHaveBeenCalled();
|
||||
expect(deleteWhere).toHaveBeenCalled();
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ purgedRows: 4 }),
|
||||
"Purged archived audit rows",
|
||||
);
|
||||
// COMPLETE clears the state key.
|
||||
expect(log.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ rowCount: 4 }),
|
||||
"Audit archival complete",
|
||||
);
|
||||
});
|
||||
|
||||
it("works without a logger argument on the full happy path", async () => {
|
||||
const deleteWhere = vi.fn().mockResolvedValue({ rowCount: 1 });
|
||||
const { runAuditArchive } = await loadAuditArchive();
|
||||
deleteMock.mockReturnValue({ where: deleteWhere });
|
||||
isFeatureEnabledMock.mockReturnValue(true);
|
||||
selectMock
|
||||
.mockReturnValueOnce(queryChain([{ value: "6" }]))
|
||||
.mockReturnValueOnce(queryChain([]))
|
||||
.mockReturnValueOnce(queryChain([{ id: "a1" }]));
|
||||
|
||||
// No log passed: every log?.xxx call must no-op without throwing.
|
||||
await expect(runAuditArchive()).resolves.toBeUndefined();
|
||||
expect(pipelineMock).toHaveBeenCalledTimes(1);
|
||||
expect(deleteWhere).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user