mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: expand coverage across jobs and tools (#380)
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const insertedValues = vi.hoisted(() => vi.fn());
|
||||
const queueAdd = vi.hoisted(() => vi.fn());
|
||||
const getJob = vi.hoisted(() => vi.fn());
|
||||
const queueEventClose = vi.hoisted(() => vi.fn());
|
||||
const flowProducerClose = vi.hoisted(() => vi.fn());
|
||||
|
||||
async function loadEnqueueModule() {
|
||||
vi.resetModules();
|
||||
insertedValues.mockReset();
|
||||
queueAdd.mockReset();
|
||||
getJob.mockReset();
|
||||
queueEventClose.mockReset();
|
||||
flowProducerClose.mockReset();
|
||||
|
||||
queueAdd.mockResolvedValue({ id: "job-1" });
|
||||
queueEventClose.mockResolvedValue(undefined);
|
||||
flowProducerClose.mockResolvedValue(undefined);
|
||||
|
||||
vi.doMock("bullmq", () => ({
|
||||
QueueEvents: vi.fn(() => ({
|
||||
close: queueEventClose,
|
||||
waitUntilReady: vi.fn().mockResolvedValue(undefined),
|
||||
})),
|
||||
FlowProducer: vi.fn(() => ({
|
||||
close: flowProducerClose,
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.doMock("../../../../apps/api/src/config.js", () => ({
|
||||
env: { SYNC_WAIT_MS: 50 },
|
||||
}));
|
||||
|
||||
vi.doMock("../../../../apps/api/src/db/index.js", () => ({
|
||||
db: {
|
||||
insert: vi.fn(() => ({
|
||||
values: insertedValues.mockResolvedValue(undefined),
|
||||
})),
|
||||
},
|
||||
schema: {
|
||||
jobs: {},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.doMock("../../../../apps/api/src/jobs/connection.js", () => ({
|
||||
createBullMQConnection: vi.fn(() => ({ mocked: "connection" })),
|
||||
}));
|
||||
|
||||
vi.doMock("../../../../apps/api/src/jobs/queues.js", () => ({
|
||||
getQueue: vi.fn(() => ({
|
||||
add: queueAdd,
|
||||
getJob,
|
||||
})),
|
||||
}));
|
||||
|
||||
return import("../../../../apps/api/src/jobs/enqueue.js");
|
||||
}
|
||||
|
||||
describe("job enqueue helpers", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("strips NUL bytes recursively before persisting settings but keeps queue data intact", async () => {
|
||||
const { enqueueToolJob } = await loadEnqueueModule();
|
||||
const data = {
|
||||
jobId: "job-1",
|
||||
userId: null,
|
||||
toolId: "tool-a",
|
||||
pool: "image",
|
||||
kind: "single",
|
||||
inputRefs: ["uploads/job-1/input.png"],
|
||||
filename: "input.png",
|
||||
settings: {
|
||||
title: "a\0b",
|
||||
nested: { value: "c\0d" },
|
||||
list: ["e\0f", 1],
|
||||
},
|
||||
} as never;
|
||||
|
||||
await enqueueToolJob(data);
|
||||
|
||||
expect(insertedValues).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "job-1",
|
||||
settings: {
|
||||
title: "ab",
|
||||
nested: { value: "cd" },
|
||||
list: ["ef", 1],
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(queueAdd).toHaveBeenCalledWith(
|
||||
"tool-a",
|
||||
expect.objectContaining({
|
||||
settings: {
|
||||
title: "a\0b",
|
||||
nested: { value: "c\0d" },
|
||||
list: ["e\0f", 1],
|
||||
},
|
||||
}),
|
||||
{ jobId: "job-1" },
|
||||
);
|
||||
});
|
||||
|
||||
it("persists redacted dbSettings while enqueueing real settings", async () => {
|
||||
const { enqueueToolJob } = await loadEnqueueModule();
|
||||
|
||||
await enqueueToolJob({
|
||||
jobId: "job-2",
|
||||
userId: null,
|
||||
toolId: "ftp-upload",
|
||||
pool: "system",
|
||||
kind: "single",
|
||||
inputRefs: [],
|
||||
filename: "file.txt",
|
||||
settings: { password: "secret" },
|
||||
dbSettings: { password: "[redacted]" },
|
||||
} as never);
|
||||
|
||||
expect(insertedValues).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ settings: { password: "[redacted]" } }),
|
||||
);
|
||||
expect(queueAdd).toHaveBeenCalledWith(
|
||||
"ftp-upload",
|
||||
expect.objectContaining({ settings: { password: "secret" } }),
|
||||
{ jobId: "job-2" },
|
||||
);
|
||||
});
|
||||
|
||||
it("waitForJob returns null when the job is missing or the sync window times out", async () => {
|
||||
const { waitForJob } = await loadEnqueueModule();
|
||||
|
||||
getJob.mockResolvedValueOnce(undefined);
|
||||
await expect(waitForJob("image", "missing")).resolves.toBeNull();
|
||||
|
||||
getJob.mockResolvedValueOnce({
|
||||
waitUntilFinished: vi.fn().mockRejectedValue(new Error("job timed out before finishing")),
|
||||
});
|
||||
await expect(waitForJob("image", "slow", 25)).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("waitForJob rethrows real job failures", async () => {
|
||||
const { waitForJob } = await loadEnqueueModule();
|
||||
getJob.mockResolvedValueOnce({
|
||||
waitUntilFinished: vi.fn().mockRejectedValue(new Error("processor failed")),
|
||||
});
|
||||
|
||||
await expect(waitForJob("image", "failed")).rejects.toThrow("processor failed");
|
||||
});
|
||||
|
||||
it("closes lazy QueueEvents and FlowProducer singletons", async () => {
|
||||
const { closeFlowProducer, closeQueueEvents, getFlowProducer, warmQueueEvents, waitForJob } =
|
||||
await loadEnqueueModule();
|
||||
|
||||
await warmQueueEvents();
|
||||
getFlowProducer();
|
||||
getJob.mockResolvedValueOnce(undefined);
|
||||
await waitForJob("image", "job-1");
|
||||
|
||||
await closeQueueEvents();
|
||||
await closeFlowProducer();
|
||||
|
||||
expect(queueEventClose).toHaveBeenCalled();
|
||||
expect(flowProducerClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user