import { expect, openSettings, test } from "./helpers"; // --------------------------------------------------------------------------- // Settings Dialog -- API Keys tab (full coverage) // --------------------------------------------------------------------------- test.describe("GUI Settings - API Keys Tab", () => { test("navigates to API Keys tab and shows heading", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); await expect(page.locator("h3").filter({ hasText: "API Keys" })).toBeVisible(); await expect(page.getByText("Manage API keys for programmatic access")).toBeVisible(); }); test("shows Generate API Key button and name input", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); await expect(page.getByPlaceholder("Key name (optional)")).toBeVisible(); await expect(page.getByRole("button", { name: /generate api key/i })).toBeVisible(); }); test("generating an API key displays the key with copy button", async ({ loggedInPage: page, }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); const keyName = `apiKeyTest-${Date.now()}`; await page.getByPlaceholder("Key name (optional)").fill(keyName); await page.getByRole("button", { name: /generate api key/i }).click(); // The key should appear in a code element const keyDisplay = page.locator("code.font-mono"); await expect(keyDisplay).toBeVisible({ timeout: 5_000 }); // Copy button should be visible next to the key await expect(page.locator("button[title='Copy']")).toBeVisible(); // The "Store this key" warning should be visible await expect(page.getByText("Store this key securely")).toBeVisible(); // Clean up: delete the key we just created const deleteBtn = page.locator("button[title='Delete key']").first(); if (await deleteBtn.isVisible()) { page.on("dialog", (d) => d.accept()); await deleteBtn.click(); } }); test("generated key appears in Existing Keys list", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); const keyName = `listTest-${Date.now()}`; await page.getByPlaceholder("Key name (optional)").fill(keyName); await page.getByRole("button", { name: /generate api key/i }).click(); // Wait for key to appear await expect(page.locator("code.font-mono")).toBeVisible({ timeout: 5_000 }); // The key name should appear in the "Existing Keys" section await expect(page.getByText("Existing Keys")).toBeVisible(); await expect(page.getByText(keyName)).toBeVisible(); // Clean up page.on("dialog", (d) => d.accept()); await page.locator("button[title='Delete key']").first().click(); await page.waitForTimeout(500); }); test("permission scoping toggle reveals checkboxes", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); const scopingToggle = page.getByText("Restrict permissions (optional)"); await expect(scopingToggle).toBeVisible(); await scopingToggle.click(); // After expanding, the collapse text and permission checkboxes appear await expect(page.getByText("Remove permission scoping")).toBeVisible(); await expect(page.locator("input[type='checkbox']").first()).toBeVisible(); await expect(page.getByText("tools:use")).toBeVisible(); }); test("permission scoping checkboxes can be toggled", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); // Open scoping panel await page.getByText("Restrict permissions (optional)").click(); await expect(page.getByText("Remove permission scoping")).toBeVisible(); // Find the first checkbox and verify it can be checked const firstCheckbox = page.locator("input[type='checkbox']").first(); await expect(firstCheckbox).not.toBeChecked(); await firstCheckbox.check(); await expect(firstCheckbox).toBeChecked(); // Uncheck it await firstCheckbox.uncheck(); await expect(firstCheckbox).not.toBeChecked(); }); test("deleting an API key removes it from the list", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); // Create a key to delete const keyName = `deleteTest-${Date.now()}`; await page.getByPlaceholder("Key name (optional)").fill(keyName); await page.getByRole("button", { name: /generate api key/i }).click(); await expect(page.locator("code.font-mono")).toBeVisible({ timeout: 5_000 }); // Confirm the key appears await expect(page.getByText(keyName)).toBeVisible(); // Delete it page.on("dialog", (d) => d.accept()); await page.locator("button[title='Delete key']").first().click(); // Key name should disappear await expect(page.getByText(keyName)).not.toBeVisible({ timeout: 5_000 }); }); test("expiration date input is available", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); await expect(page.getByText("Expires:")).toBeVisible(); await expect(page.locator("input[type='datetime-local']")).toBeVisible(); }); test("expiration date Clear button works", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); const dateInput = page.locator("input[type='datetime-local']"); await dateInput.fill("2030-12-31T23:59"); // Clear button should appear after setting a date const clearBtn = page.getByRole("button", { name: /clear/i }); await expect(clearBtn).toBeVisible(); await clearBtn.click(); // Input should be empty again await expect(dateInput).toHaveValue(""); }); test("empty state shows no keys message or existing keys list", async ({ loggedInPage: page, }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); // Wait for the API Keys heading to confirm the section loaded await expect(page.locator("h3").filter({ hasText: "API Keys" })).toBeVisible(); // Wait for Generate button to appear (confirms loading is complete) await expect(page.getByRole("button", { name: /generate api key/i })).toBeVisible(); // If no keys exist, the empty message should be shown; otherwise existing keys list const noKeysText = page.getByText("No API keys yet. Generate one to get started."); const existingKeysHeader = page.getByText("Existing Keys"); // Wait briefly for state to settle after loading await page.waitForTimeout(500); const noKeysVisible = await noKeysText.isVisible().catch(() => false); const existingVisible = await existingKeysHeader.isVisible().catch(() => false); // One of the two states must be true expect(noKeysVisible || existingVisible).toBe(true); }); test("generating a key with scoped permissions creates a scoped key", async ({ loggedInPage: page, }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); // Set a key name const keyName = `scopedKey-${Date.now()}`; await page.getByPlaceholder("Key name (optional)").fill(keyName); // Open scoping and select a permission await page.getByText("Restrict permissions (optional)").click(); await expect(page.getByText("Remove permission scoping")).toBeVisible(); // Check the first permission checkbox await page.locator("input[type='checkbox']").first().check(); // Generate the key await page.getByRole("button", { name: /generate api key/i }).click(); await expect(page.locator("code.font-mono")).toBeVisible({ timeout: 5_000 }); // The scoped key should show its permissions in the Existing Keys list await expect(page.getByText("Scoped:")).toBeVisible(); // Clean up page.on("dialog", (d) => d.accept()); await page.locator("button[title='Delete key']").first().click(); await page.waitForTimeout(500); }); test("generated key starts with si_ prefix", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); const keyName = `prefixTest-${Date.now()}`; await page.getByPlaceholder("Key name (optional)").fill(keyName); await page.getByRole("button", { name: /generate api key/i }).click(); // The key should appear in a code element const keyDisplay = page.locator("code.font-mono"); await expect(keyDisplay).toBeVisible({ timeout: 5_000 }); // Verify the key text starts with "si_" const keyText = await keyDisplay.textContent(); expect(keyText).toMatch(/^si_/); // Clean up page.on("dialog", (d) => d.accept()); await page.locator("button[title='Delete key']").first().click(); await page.waitForTimeout(500); }); test("existing keys show prefix and creation date", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); const keyName = `metaTest-${Date.now()}`; await page.getByPlaceholder("Key name (optional)").fill(keyName); await page.getByRole("button", { name: /generate api key/i }).click(); await expect(page.locator("code.font-mono")).toBeVisible({ timeout: 5_000 }); // In the Existing Keys list, the key entry should show the prefix (si_...) await expect(page.getByText("Existing Keys")).toBeVisible(); await expect(page.getByText(/si_/).first()).toBeVisible(); // Clean up page.on("dialog", (d) => d.accept()); await page.locator("button[title='Delete key']").first().click(); await page.waitForTimeout(500); }); test("generating a key without a name still works", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); // Leave the name field empty and generate await page.getByPlaceholder("Key name (optional)").fill(""); await page.getByRole("button", { name: /generate api key/i }).click(); // The key should still appear const keyDisplay = page.locator("code.font-mono"); await expect(keyDisplay).toBeVisible({ timeout: 5_000 }); await expect(page.getByText("Store this key securely")).toBeVisible(); // Clean up page.on("dialog", (d) => d.accept()); await page.locator("button[title='Delete key']").first().click(); await page.waitForTimeout(500); }); test("cancel on delete confirmation keeps the key", async ({ loggedInPage: page }) => { await openSettings(page); await page.getByRole("button", { name: /api keys/i }).click(); const keyName = `cancelDelete-${Date.now()}`; await page.getByPlaceholder("Key name (optional)").fill(keyName); await page.getByRole("button", { name: /generate api key/i }).click(); await expect(page.locator("code.font-mono")).toBeVisible({ timeout: 5_000 }); // Dismiss the confirm dialog to cancel deletion page.on("dialog", (d) => d.dismiss()); await page.locator("button[title='Delete key']").first().click(); // Key should still be present await expect(page.getByText(keyName)).toBeVisible(); // Now actually delete for cleanup page.removeAllListeners("dialog"); page.on("dialog", (d) => d.accept()); await page.locator("button[title='Delete key']").first().click(); await page.waitForTimeout(500); }); });