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:
@@ -1,8 +1,11 @@
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, mkdtemp, rm, unlink, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { env } from "../../../apps/api/src/config.js";
|
||||
import {
|
||||
assertLocalCapacity,
|
||||
computeWorkspaceUsedBytes,
|
||||
isOverWorkspaceCap,
|
||||
} from "../../../apps/api/src/lib/object-storage.js";
|
||||
@@ -43,3 +46,93 @@ describe("computeWorkspaceUsedBytes", () => {
|
||||
expect(await computeWorkspaceUsedBytes(join(root, "nope"))).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// assertLocalCapacity wires the aggregate size cap and the free-space floor to
|
||||
// the real WORKSPACE_PATH. This file never calls putObject, so the module's
|
||||
// 30s workspace-size cache starts empty; each test picks a base timestamp far
|
||||
// past any prior one so its first check recomputes rather than reusing a stale
|
||||
// cached total.
|
||||
describe("assertLocalCapacity capacity guard", () => {
|
||||
const originalWorkspace = env.WORKSPACE_PATH;
|
||||
const originalMaxGb = env.MAX_WORKSPACE_SIZE_GB;
|
||||
let root = "";
|
||||
let nowBase = 1_000_000_000_000;
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = originalWorkspace;
|
||||
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = originalMaxGb;
|
||||
if (root) await rm(root, { recursive: true, force: true });
|
||||
root = "";
|
||||
// Advance the base well past the 30s cache window for the next test.
|
||||
nowBase += 10 * 60_000;
|
||||
});
|
||||
|
||||
it("returns without error when the workspace root does not exist yet", async () => {
|
||||
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = join(
|
||||
tmpdir(),
|
||||
`snapotter-absent-${process.pid}-${Date.now()}`,
|
||||
);
|
||||
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = 0.000001;
|
||||
await expect(assertLocalCapacity()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("passes when usage is under the configured aggregate cap", async () => {
|
||||
root = await mkdtemp(join(tmpdir(), "snapotter-cap-under-"));
|
||||
await mkdir(join(root, "uploads", "job1"), { recursive: true });
|
||||
await writeFile(join(root, "uploads", "job1", "a.bin"), Buffer.alloc(4096));
|
||||
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = root;
|
||||
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = 10;
|
||||
vi.spyOn(Date, "now").mockReturnValue(nowBase);
|
||||
|
||||
await expect(assertLocalCapacity()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not enforce the aggregate cap when MAX_WORKSPACE_SIZE_GB is 0", async () => {
|
||||
root = await mkdtemp(join(tmpdir(), "snapotter-cap-off-"));
|
||||
await mkdir(join(root, "outputs", "job1"), { recursive: true });
|
||||
await writeFile(join(root, "outputs", "job1", "big.bin"), Buffer.alloc(2 * 1024 * 1024));
|
||||
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = root;
|
||||
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = 0;
|
||||
vi.spyOn(Date, "now").mockReturnValue(nowBase);
|
||||
|
||||
// Cap disabled: even a tiny disk must not 503 on the cap path. statfs on a
|
||||
// real temp dir reports the (ample) host free space, so the floor passes too.
|
||||
await expect(assertLocalCapacity()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("throws a 503 when aggregate usage exceeds the cap", async () => {
|
||||
root = await mkdtemp(join(tmpdir(), "snapotter-cap-over-"));
|
||||
await mkdir(join(root, "outputs", "job1"), { recursive: true });
|
||||
// 2 MiB of files; cap of 0.001 GB (~1 MiB) is exceeded.
|
||||
await writeFile(join(root, "outputs", "job1", "big.bin"), Buffer.alloc(2 * 1024 * 1024));
|
||||
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = root;
|
||||
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = 0.001;
|
||||
vi.spyOn(Date, "now").mockReturnValue(nowBase);
|
||||
|
||||
await expect(assertLocalCapacity()).rejects.toMatchObject({
|
||||
statusCode: 503,
|
||||
message: expect.stringMatching(/storage limit/i),
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses the cached workspace total within the cache window", async () => {
|
||||
root = await mkdtemp(join(tmpdir(), "snapotter-cap-cache-"));
|
||||
await mkdir(join(root, "outputs", "job1"), { recursive: true });
|
||||
const big = join(root, "outputs", "job1", "big.bin");
|
||||
await writeFile(big, Buffer.alloc(2 * 1024 * 1024));
|
||||
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = root;
|
||||
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = 0.001;
|
||||
// Freeze time so both calls fall inside the 30s window.
|
||||
vi.spyOn(Date, "now").mockReturnValue(nowBase);
|
||||
|
||||
// First call computes the real total (2 MiB) and trips the cap.
|
||||
await expect(assertLocalCapacity()).rejects.toMatchObject({ statusCode: 503 });
|
||||
|
||||
// Delete every file so a fresh recompute would report 0 bytes and pass. The
|
||||
// second call still trips the cap, proving it read the cached total, not disk.
|
||||
await unlink(big);
|
||||
expect(existsSync(big)).toBe(false);
|
||||
await expect(assertLocalCapacity()).rejects.toMatchObject({ statusCode: 503 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user