// Certificate key selection mirrors XCA's unused-key filtering. const { test, expect } = require("@playwright/test"); function unique(prefix) { return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1000)}`; } async function field(page, label) { return page.locator("label").filter({ hasText: label }).first().locator("..").locator("input, select, textarea").first(); } async function login(page, username) { await page.goto("/login"); await page.getByRole("link", { name: "Sign in with default" }).click(); await page.getByPlaceholder("Enter any user/subject").fill(username); await page.getByRole("button", { name: "Sign-in" }).click(); await expect(page.getByRole("heading", { name: "Vaults" })).toBeVisible(); } function existingKeySelect(page) { return page.locator("label").filter({ hasText: "Existing key" }).locator("xpath=following-sibling::select[1]"); } test("certificate key selection hides used keys unless requested", async ({ page }) => { const user = unique("key-selection"); const vaultName = unique("Key Selection Vault"); const usedKeyName = unique("used-key"); const unusedKeyName = unique("unused-key"); const certName = unique("key-selection.example"); await login(page, user); await page.getByRole("button", { name: "New vault" }).click(); await page.getByPlaceholder("Production PKI").fill(vaultName); await page.getByRole("button", { name: "Create vault" }).click(); await page.getByRole("link", { name: vaultName }).click(); const vaultUrl = page.url(); for (const keyName of [usedKeyName, unusedKeyName]) { await page.goto(`${vaultUrl}/keys/new`); await (await field(page, "Internal name")).fill(keyName); await page.getByRole("button", { name: "Generate key" }).click(); await expect(page.getByRole("heading", { name: keyName })).toBeVisible(); } // Use one key in a certificate so subsequent forms can distinguish it. await page.goto(`${vaultUrl}/certificates/new`); let keySelect = existingKeySelect(page); const usedKeyOption = keySelect.locator("option", { hasText: usedKeyName }); await expect(usedKeyOption).toHaveCount(1); const usedKeyId = await usedKeyOption.getAttribute("value"); expect(usedKeyId).toBeTruthy(); await keySelect.selectOption(usedKeyId); await (await field(page, "Internal name")).fill(certName); await (await field(page, "Common name (CN)")).fill(certName); await page.getByRole("button", { name: "Create certificate" }).click(); await expect(page.getByRole("heading", { name: certName })).toBeVisible(); const certUrl = page.url(); // Copying a certificate includes used keys and retains the original key. await page.goto(`${certUrl}/similar`); keySelect = existingKeySelect(page); let includeUsed = page.getByLabel("Include used keys", { exact: true }); await expect(includeUsed).toBeChecked(); await expect(keySelect).toHaveValue(usedKeyId); await expect(keySelect.locator("option", { hasText: usedKeyName })).toHaveCount(1); // New certificates show only unused private keys by default. await page.goto(`${vaultUrl}/certificates/new`); keySelect = existingKeySelect(page); includeUsed = page.getByLabel("Include used keys", { exact: true }); await expect(includeUsed).not.toBeChecked(); await expect(keySelect.locator("option", { hasText: unusedKeyName })).toHaveCount(1); await expect(keySelect.locator("option", { hasText: usedKeyName })).toHaveCount(0); await includeUsed.check(); await expect(keySelect.locator("option", { hasText: usedKeyName })).toHaveCount(1); await expect(keySelect.locator("option", { hasText: unusedKeyName })).toHaveCount(1); // Hiding a selected used key also clears the form value. await keySelect.selectOption(usedKeyId); await includeUsed.uncheck(); await expect(keySelect).toHaveValue(""); await expect(keySelect.locator("option", { hasText: usedKeyName })).toHaveCount(0); });