mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(tools): 2.0 phase 5 wave 3b - audio depth (14 tools) (#222)
This commit is contained in:
@@ -136,7 +136,7 @@ vi.mock("sharp", () => ({
|
||||
|
||||
// ── Imports ─────────────────────────────────────────────────────────────
|
||||
|
||||
import { waitForJob } from "../../../apps/api/src/jobs/enqueue.js";
|
||||
import { enqueueToolJob, waitForJob } from "../../../apps/api/src/jobs/enqueue.js";
|
||||
import { isToolInstalled } from "../../../apps/api/src/lib/feature-status.js";
|
||||
import { validateImageBuffer } from "../../../apps/api/src/lib/file-validation.js";
|
||||
import type { AnyToolRouteConfig } from "../../../apps/api/src/routes/tool-factory.js";
|
||||
@@ -575,4 +575,149 @@ describe("createToolRoute", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("multipart field recovery", () => {
|
||||
it("recovers settings from part.fields when the iterator drops trailing fields", async () => {
|
||||
const app = createMockApp();
|
||||
const id = uniqueId();
|
||||
createToolRoute(app as never, makeMockConfig(id));
|
||||
const handler = app.routes[`/api/v1/tools/${id}`];
|
||||
const reply = createMockReply();
|
||||
|
||||
// Simulate the @fastify/multipart race: the iterator yields only the
|
||||
// file part; the settings field is present only on part.fields.
|
||||
const req = {
|
||||
parts: () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
type: "file",
|
||||
filename: "test.png",
|
||||
file: (async function* () {
|
||||
yield Buffer.from("png-data");
|
||||
})(),
|
||||
fields: {
|
||||
settings: { value: '{"x":1}' },
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
log: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
|
||||
};
|
||||
|
||||
await handler(req, reply);
|
||||
|
||||
// Settings must be recovered as {x:1}, not fall through to defaults ({})
|
||||
const enqueueCall = vi.mocked(enqueueToolJob).mock.calls[0][0];
|
||||
expect(enqueueCall.settings).toEqual({ x: 1 });
|
||||
expect(reply.send).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ jobId: expect.any(String) }),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not overwrite settings already collected from the iterator", async () => {
|
||||
const app = createMockApp();
|
||||
const id = uniqueId();
|
||||
createToolRoute(app as never, makeMockConfig(id));
|
||||
const handler = app.routes[`/api/v1/tools/${id}`];
|
||||
const reply = createMockReply();
|
||||
|
||||
// Both the iterator and part.fields carry settings; the iterator value wins
|
||||
const req = {
|
||||
parts: () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
type: "file",
|
||||
filename: "test.png",
|
||||
file: (async function* () {
|
||||
yield Buffer.from("png-data");
|
||||
})(),
|
||||
fields: {
|
||||
settings: { value: '{"from":"fields"}' },
|
||||
},
|
||||
};
|
||||
yield {
|
||||
type: "field",
|
||||
fieldname: "settings",
|
||||
value: '{"from":"iterator"}',
|
||||
file: (async function* () {})(),
|
||||
fields: {
|
||||
settings: { value: '{"from":"fields"}' },
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
log: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
|
||||
};
|
||||
|
||||
await handler(req, reply);
|
||||
|
||||
const enqueueCall = vi.mocked(enqueueToolJob).mock.calls[0][0];
|
||||
expect(enqueueCall.settings).toEqual({ from: "iterator" });
|
||||
});
|
||||
|
||||
it("recovers fileId and clientJobId from part.fields", async () => {
|
||||
const app = createMockApp();
|
||||
const id = uniqueId();
|
||||
createToolRoute(app as never, makeMockConfig(id));
|
||||
const handler = app.routes[`/api/v1/tools/${id}`];
|
||||
const reply = createMockReply();
|
||||
|
||||
const req = {
|
||||
parts: () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
type: "file",
|
||||
filename: "test.png",
|
||||
file: (async function* () {
|
||||
yield Buffer.from("png-data");
|
||||
})(),
|
||||
fields: {
|
||||
settings: { value: "{}" },
|
||||
fileId: { value: "f-123" },
|
||||
clientJobId: { value: "cj-456" },
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
log: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
|
||||
};
|
||||
|
||||
await handler(req, reply);
|
||||
|
||||
const enqueueCall = vi.mocked(enqueueToolJob).mock.calls[0][0];
|
||||
expect(enqueueCall.fileId).toBe("f-123");
|
||||
expect(enqueueCall.clientJobId).toBe("cj-456");
|
||||
});
|
||||
|
||||
it("handles array-form fields from part.fields", async () => {
|
||||
const app = createMockApp();
|
||||
const id = uniqueId();
|
||||
createToolRoute(app as never, makeMockConfig(id));
|
||||
const handler = app.routes[`/api/v1/tools/${id}`];
|
||||
const reply = createMockReply();
|
||||
|
||||
const req = {
|
||||
parts: () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
type: "file",
|
||||
filename: "test.png",
|
||||
file: (async function* () {
|
||||
yield Buffer.from("png-data");
|
||||
})(),
|
||||
fields: {
|
||||
settings: [{ value: '{"arr":true}' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
log: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
|
||||
};
|
||||
|
||||
await handler(req, reply);
|
||||
|
||||
const enqueueCall = vi.mocked(enqueueToolJob).mock.calls[0][0];
|
||||
expect(enqueueCall.settings).toEqual({ arr: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user