Files
SnapOtter/tests/integration/content-aware-crop.test.ts
T

231 lines
6.7 KiB
TypeScript

/**
* Integration tests for the content-aware-crop AI tool (/api/v1/tools/content-aware-crop).
*
* This tool uses async processing: valid requests return 202 with a jobId,
* and the result is delivered via SSE. The Python sidecar is not available
* in the test environment, so the route returns 501 (not installed).
* Validation paths are always testable regardless of sidecar availability.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { buildTestApp, createMultipartPayload, loginAsAdmin, type TestApp } from "./test-server.js";
const FIXTURES = join(__dirname, "..", "fixtures");
const PNG = readFileSync(join(FIXTURES, "test-200x150.png"));
let testApp: TestApp;
let app: TestApp["app"];
let adminToken: string;
beforeAll(async () => {
testApp = await buildTestApp();
app = testApp.app;
adminToken = await loginAsAdmin(app);
}, 30_000);
afterAll(async () => {
await testApp.cleanup();
}, 10_000);
describe("Content-Aware Crop", () => {
// -- Processing (sidecar-dependent) ------------------------------------
it("returns 501 when AI sidecar is not installed", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "test.png", contentType: "image/png", content: PNG },
{
name: "settings",
content: JSON.stringify({
extendTop: 100,
extendRight: 0,
extendBottom: 100,
extendLeft: 0,
}),
},
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/content-aware-crop",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect([202, 501]).toContain(res.statusCode);
if (res.statusCode === 501) {
const json = JSON.parse(res.body);
expect(json.code).toBe("FEATURE_NOT_INSTALLED");
}
}, 60_000);
// -- Validation (always testable) --------------------------------------
it("returns 400 when no image is provided", async () => {
const { body, contentType } = createMultipartPayload([
{
name: "settings",
content: JSON.stringify({ extendTop: 100, extendRight: 0, extendBottom: 0, extendLeft: 0 }),
},
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/content-aware-crop",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
// 400 when sidecar is available (validation runs), 501 when not (isToolInstalled check fires first)
expect([400, 501]).toContain(res.statusCode);
if (res.statusCode === 400) {
const json = JSON.parse(res.body);
expect(json.error).toMatch(/no image/i);
}
});
it("returns 400 when no settings are provided", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "test.png", contentType: "image/png", content: PNG },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/content-aware-crop",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
// Without settings, defaults apply: all extends = 0 => "at least one direction" error
// But 501 fires first if sidecar is not installed
expect([400, 501]).toContain(res.statusCode);
if (res.statusCode === 400) {
const json = JSON.parse(res.body);
expect(json.error).toMatch(/at least one extend direction/i);
}
});
it("returns 400 when all extensions are zero", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "test.png", contentType: "image/png", content: PNG },
{
name: "settings",
content: JSON.stringify({ extendTop: 0, extendRight: 0, extendBottom: 0, extendLeft: 0 }),
},
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/content-aware-crop",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect([400, 501]).toContain(res.statusCode);
if (res.statusCode === 400) {
const json = JSON.parse(res.body);
expect(json.error).toMatch(/at least one extend direction/i);
}
});
it("returns 400 with negative extension values", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "test.png", contentType: "image/png", content: PNG },
{
name: "settings",
content: JSON.stringify({
extendTop: -10,
extendRight: -5,
extendBottom: -20,
extendLeft: -15,
}),
},
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/content-aware-crop",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect([400, 501]).toContain(res.statusCode);
if (res.statusCode === 400) {
const json = JSON.parse(res.body);
expect(json.error).toMatch(/invalid settings/i);
}
});
it("accepts valid request format (202 or 501)", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "test.png", contentType: "image/png", content: PNG },
{
name: "settings",
content: JSON.stringify({
extendTop: 100,
extendRight: 0,
extendBottom: 100,
extendLeft: 0,
}),
},
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/content-aware-crop",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect([202, 501]).toContain(res.statusCode);
if (res.statusCode === 202) {
const json = JSON.parse(res.body);
expect(json.jobId).toBeDefined();
expect(json.async).toBe(true);
}
}, 60_000);
it("rejects unauthenticated requests (401)", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "test.png", contentType: "image/png", content: PNG },
{
name: "settings",
content: JSON.stringify({
extendTop: 50,
extendRight: 50,
extendBottom: 50,
extendLeft: 50,
}),
},
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/content-aware-crop",
headers: { "content-type": contentType },
body,
});
expect(res.statusCode).toBe(401);
});
});