Files
SnapOtter/tests/unit/api/object-storage-statfs.test.ts
T
SnapOtterandGitHub 301e6eb01a 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.
2026-07-24 17:36:57 +08:00

85 lines
3.5 KiB
TypeScript

// Covers assertLocalCapacity's statfs free-space floor in object-storage.ts
// (source L155-166), which the workspace-cap suite leaves as no-cov because it
// only exercises the aggregate-size branch. statfs cannot be spied in ESM (its
// namespace is non-configurable), so this file mocks node:fs/promises, spreading
// the real module and overriding only statfs. The aggregate cap is disabled here
// (MAX_WORKSPACE_SIZE_GB=0), so assertWorkspaceSizeCap returns before touching
// any other fs/promises call and the spread keeps real behavior everywhere else.
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const statfsMock = vi.hoisted(() => vi.fn());
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs/promises")>();
return { ...actual, statfs: statfsMock };
});
import { env } from "../../../apps/api/src/config.js";
import {
assertLocalCapacity,
CAPACITY_CRITICAL_GB,
} from "../../../apps/api/src/lib/object-storage.js";
describe("assertLocalCapacity free-space floor (statfs)", () => {
const originalWorkspace = env.WORKSPACE_PATH;
const originalMaxGb = env.MAX_WORKSPACE_SIZE_GB;
let root = "";
// Each test picks a base timestamp far past any prior so the 30s workspace-size
// cache never serves a stale total; the cap is off anyway, but keep it clean.
let nowBase = 3_000_000_000_000;
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), "snapotter-statfs-floor-"));
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = root;
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = 0;
statfsMock.mockReset();
vi.spyOn(Date, "now").mockReturnValue(nowBase);
});
afterEach(() => {
vi.restoreAllMocks();
(env as { WORKSPACE_PATH: string }).WORKSPACE_PATH = originalWorkspace;
(env as { MAX_WORKSPACE_SIZE_GB: number }).MAX_WORKSPACE_SIZE_GB = originalMaxGb;
if (root) rmSync(root, { recursive: true, force: true });
root = "";
nowBase += 10 * 60_000;
});
it("throws 503 when free space (bavail*bsize) is just under the 0.5 GiB floor", async () => {
// One byte under the floor: exercises freeBytes computation and the strict <.
const floorBytes = CAPACITY_CRITICAL_GB * 1024 ** 3;
statfsMock.mockResolvedValue({ bavail: floorBytes - 1, bsize: 1 });
await expect(assertLocalCapacity()).rejects.toMatchObject({
statusCode: 503,
message: expect.stringMatching(/disk space/i),
});
});
it("passes when free space sits exactly at the floor (boundary is NOT below)", async () => {
const floorBytes = CAPACITY_CRITICAL_GB * 1024 ** 3;
statfsMock.mockResolvedValue({ bavail: floorBytes, bsize: 1 });
await expect(assertLocalCapacity()).resolves.toBeUndefined();
});
it("multiplies bavail by bsize (product, not either factor alone)", async () => {
// Neither factor alone clears the floor, but their product (2 GiB) does. A
// mutation of `*` to a single operand or `+` would drop below and 503.
statfsMock.mockResolvedValue({ bavail: 4 * 1024 * 1024, bsize: 512 });
await expect(assertLocalCapacity()).resolves.toBeUndefined();
expect(statfsMock).toHaveBeenCalledWith(root);
});
it("allows the write when statfs itself rejects (unavailable on the platform)", async () => {
statfsMock.mockRejectedValue(Object.assign(new Error("ENOSYS"), { code: "ENOSYS" }));
await expect(assertLocalCapacity()).resolves.toBeUndefined();
});
});