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 = { "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(); } } }); });