mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
E2E tests cover feature API responses, tool route 501 guards for all 11 AI tools, batch endpoint guards, and GUI interactions (install prompt, settings panel, sidebar badges). Unit tests add edge cases for duplicate tool detection, unknown bundle/tool lookups, and TOOL_BUNDLE_MAP integrity.
223 lines
8.6 KiB
TypeScript
223 lines
8.6 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
// ─── Feature API tests ─────────────────────────────────────────────
|
|
|
|
test.describe("Feature API", () => {
|
|
test("GET /api/v1/features returns all 6 bundles with correct shape", async ({ request }) => {
|
|
const response = await request.get("/api/v1/features");
|
|
expect(response.ok()).toBeTruthy();
|
|
const data = await response.json();
|
|
expect(data.bundles).toHaveLength(6);
|
|
|
|
const expectedBundles = [
|
|
"background-removal",
|
|
"face-detection",
|
|
"object-eraser-colorize",
|
|
"upscale-enhance",
|
|
"photo-restoration",
|
|
"ocr",
|
|
];
|
|
for (const id of expectedBundles) {
|
|
const bundle = data.bundles.find((b: any) => b.id === id);
|
|
expect(bundle, `Bundle ${id} missing`).toBeDefined();
|
|
expect(bundle.name).toBeTruthy();
|
|
expect(bundle.description).toBeTruthy();
|
|
expect(bundle.estimatedSize).toBeTruthy();
|
|
expect(bundle.enablesTools).toBeInstanceOf(Array);
|
|
expect(bundle.enablesTools.length).toBeGreaterThan(0);
|
|
expect(["not_installed", "installed", "installing", "error"]).toContain(bundle.status);
|
|
}
|
|
});
|
|
|
|
test("each bundle has the correct tools", async ({ request }) => {
|
|
const response = await request.get("/api/v1/features");
|
|
const data = await response.json();
|
|
|
|
const toolMap: Record<string, string[]> = {
|
|
"background-removal": ["remove-background", "passport-photo"],
|
|
"face-detection": ["blur-faces", "red-eye-removal", "smart-crop"],
|
|
"object-eraser-colorize": ["erase-object", "colorize"],
|
|
"upscale-enhance": ["upscale", "enhance-faces", "noise-removal"],
|
|
"photo-restoration": ["restore-photo"],
|
|
ocr: ["ocr"],
|
|
};
|
|
|
|
for (const [bundleId, expectedTools] of Object.entries(toolMap)) {
|
|
const bundle = data.bundles.find((b: any) => b.id === bundleId);
|
|
expect(bundle.enablesTools).toEqual(expectedTools);
|
|
}
|
|
});
|
|
|
|
test("POST install returns 404 for unknown bundle", async ({ request }) => {
|
|
const response = await request.post("/api/v1/admin/features/nonexistent/install");
|
|
expect(response.status()).toBe(404);
|
|
});
|
|
|
|
test("POST uninstall returns 409 for not-installed bundle", async ({ request }) => {
|
|
const response = await request.post("/api/v1/admin/features/background-removal/uninstall");
|
|
expect(response.status()).toBe(409);
|
|
});
|
|
|
|
test("GET disk-usage returns totalBytes", async ({ request }) => {
|
|
const response = await request.get("/api/v1/admin/features/disk-usage");
|
|
expect(response.ok()).toBeTruthy();
|
|
const data = await response.json();
|
|
expect(typeof data.totalBytes).toBe("number");
|
|
});
|
|
});
|
|
|
|
// ─── Tool route guard tests ─────────────────────────────────────────
|
|
|
|
test.describe("Tool route guards", () => {
|
|
const pngBuffer = Buffer.from(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
|
|
"base64",
|
|
);
|
|
|
|
const aiTools = [
|
|
{ tool: "remove-background", bundle: "background-removal" },
|
|
{ tool: "upscale", bundle: "upscale-enhance" },
|
|
{ tool: "blur-faces", bundle: "face-detection" },
|
|
{ tool: "erase-object", bundle: "object-eraser-colorize" },
|
|
{ tool: "ocr", bundle: "ocr" },
|
|
{ tool: "colorize", bundle: "object-eraser-colorize" },
|
|
{ tool: "enhance-faces", bundle: "upscale-enhance" },
|
|
{ tool: "noise-removal", bundle: "upscale-enhance" },
|
|
{ tool: "red-eye-removal", bundle: "face-detection" },
|
|
{ tool: "restore-photo", bundle: "photo-restoration" },
|
|
{ tool: "passport-photo", bundle: "background-removal" },
|
|
];
|
|
|
|
for (const { tool, bundle } of aiTools) {
|
|
test(`${tool} returns 501 FEATURE_NOT_INSTALLED with correct bundle`, async ({ request }) => {
|
|
const response = await request.post(`/api/v1/tools/${tool}`, {
|
|
multipart: {
|
|
file: {
|
|
name: "test.png",
|
|
mimeType: "image/png",
|
|
buffer: pngBuffer,
|
|
},
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(response.status()).toBe(501);
|
|
const body = await response.json();
|
|
expect(body.code).toBe("FEATURE_NOT_INSTALLED");
|
|
expect(body.feature).toBe(bundle);
|
|
expect(body.featureName).toBeTruthy();
|
|
expect(body.estimatedSize).toBeTruthy();
|
|
});
|
|
}
|
|
|
|
test("non-AI tool works normally (resize)", async ({ request }) => {
|
|
const response = await request.post("/api/v1/tools/resize", {
|
|
multipart: {
|
|
file: {
|
|
name: "test.png",
|
|
mimeType: "image/png",
|
|
buffer: pngBuffer,
|
|
},
|
|
settings: JSON.stringify({ width: 100, height: 100, method: "fit" }),
|
|
},
|
|
});
|
|
// Should succeed or fail with a processing error, NOT 501
|
|
expect(response.status()).not.toBe(501);
|
|
});
|
|
});
|
|
|
|
// ─── Batch and pipeline guard tests ─────────────────────────────────
|
|
|
|
test.describe("Batch and pipeline guards", () => {
|
|
const pngBuffer = Buffer.from(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
|
|
"base64",
|
|
);
|
|
|
|
test("batch endpoint returns 501 for uninstalled AI tool", async ({ request }) => {
|
|
const response = await request.post("/api/v1/tools/remove-background/batch", {
|
|
multipart: {
|
|
"files[]": {
|
|
name: "test.png",
|
|
mimeType: "image/png",
|
|
buffer: pngBuffer,
|
|
},
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(response.status()).toBe(501);
|
|
const body = await response.json();
|
|
expect(body.code).toBe("FEATURE_NOT_INSTALLED");
|
|
});
|
|
});
|
|
|
|
// ─── GUI tests (Playwright page interactions) ───────────────────────
|
|
|
|
test.describe("Feature install UI", () => {
|
|
test("uninstalled AI tool page shows install prompt", async ({ page }) => {
|
|
await page.goto("/remove-background");
|
|
await expect(page.getByText("Background Removal")).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
await expect(page.getByText("additional download")).toBeVisible();
|
|
await expect(page.getByRole("button", { name: /enable/i })).toBeVisible();
|
|
});
|
|
|
|
test("non-AI tool page loads normally", async ({ page }) => {
|
|
await page.goto("/resize");
|
|
// Should show the normal tool UI, not an install prompt
|
|
await expect(page.getByText("additional download")).not.toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
});
|
|
|
|
test("AI tools show download badge in sidebar", async ({ page }) => {
|
|
await page.goto("/resize");
|
|
// Wait for the sidebar to load
|
|
await expect(page.locator("[data-testid='tool-panel']").or(page.locator("nav"))).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
// The download icon should be visible near AI tools
|
|
// Note: we can't easily test for the exact Download icon, but we can check the tool links exist
|
|
const removeBackgroundLink = page.locator("a[href='/remove-background']");
|
|
await expect(removeBackgroundLink).toBeVisible();
|
|
});
|
|
|
|
test("settings dialog has AI Features section", async ({ page }) => {
|
|
await page.goto("/resize");
|
|
// Open settings - look for a settings button/gear icon
|
|
const settingsButton = page
|
|
.getByRole("button", { name: /settings/i })
|
|
.or(page.locator("button[aria-label*='ettings']"));
|
|
if (await settingsButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await settingsButton.click();
|
|
await expect(page.getByText("AI Features")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
}
|
|
});
|
|
|
|
test("AI Features settings shows all 6 bundles", async ({ page }) => {
|
|
await page.goto("/resize");
|
|
const settingsButton = page
|
|
.getByRole("button", { name: /settings/i })
|
|
.or(page.locator("button[aria-label*='ettings']"));
|
|
if (await settingsButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await settingsButton.click();
|
|
// Click AI Features nav item
|
|
const aiNav = page.getByText("AI Features");
|
|
if (await aiNav.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await aiNav.click();
|
|
// Should show all 6 bundles
|
|
await expect(page.getByText("Background Removal")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
await expect(page.getByText("Face Detection")).toBeVisible();
|
|
await expect(page.getByText("Object Eraser & Colorize")).toBeVisible();
|
|
await expect(page.getByText("Upscale & Enhance")).toBeVisible();
|
|
await expect(page.getByText("Photo Restoration")).toBeVisible();
|
|
await expect(page.getByText("OCR")).toBeVisible();
|
|
}
|
|
}
|
|
});
|
|
});
|