import { execFileSync } from "node:child_process"; import { mkdtempSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "@playwright/test"; import { anmelden, E2E_PRAEFIX, keinQuerscrollen } from "./helfer"; /** Je Lauf ein eigener Schlüssel – derselbe zweimal wäre für denselben Benutzer ein Duplikat. */ function neuerSchluessel(): string { const ordner = mkdtempSync(join(tmpdir(), "pd-e2e-")); execFileSync("ssh-keygen", ["-q", "-t", "ed25519", "-N", "", "-C", "e2e@test", "-f", join(ordner, "k")]); return readFileSync(join(ordner, "k.pub"), "utf8").trim(); } test("SSH-Schlüssel hinterlegen, im Assistenten vorausgewählt, wieder entfernen", async ({ page }) => { const name = `${E2E_PRAEFIX}-${test.info().project.name}`; await anmelden(page, "/einstellungen"); // Reste eines abgebrochenen Laufs wegräumen. for (const alt of (await (await page.request.get("/api/v1/schluessel")).json()) as Array<{ id: string; name: string; }>) { if (alt.name.startsWith(E2E_PRAEFIX)) await page.request.delete(`/api/v1/schluessel/${alt.id}`); } await page.reload(); await page.locator("#schluessel-name").fill(name); await page.getByLabel("Öffentlicher Schlüssel").fill("-----BEGIN OPENSSH PRIVATE KEY-----"); await page.getByRole("button", { name: "Hinzufügen" }).click(); await expect(page.getByText(/nie den privaten Schlüssel/)).toBeVisible(); await page.getByLabel("Öffentlicher Schlüssel").fill(neuerSchluessel()); await page.getByRole("button", { name: "Hinzufügen" }).click(); await expect(page.getByText(name)).toBeVisible(); await keinQuerscrollen(page); await page.goto("/ausrollen"); if (await page.getByText("Erst einen Server anlegen").isVisible()) test.skip(true, "Kein getesteter Server vorhanden"); for (let i = 0; i < 3; i++) await page.getByRole("button", { name: "Weiter" }).click(); await page.getByLabel("Name (Hostname)").fill("pd-e2e"); for (let i = 0; i < 3; i++) await page.getByRole("button", { name: "Weiter" }).click(); await expect(page.getByLabel(name)).toBeChecked(); // Mit hinterlegtem Schlüssel geht es ohne Eingabe weiter. await expect(page.getByRole("button", { name: "Weiter" })).toBeEnabled(); await page.goto("/einstellungen"); page.once("dialog", (d) => void d.accept()); await page.getByRole("button", { name: `Schlüssel ${name} entfernen` }).click(); await expect(page.getByText(name)).toHaveCount(0); }); test("API-Token anlegen, einmal sehen, damit die API nutzen, widerrufen", async ({ page }) => { const name = `${E2E_PRAEFIX}-token-${test.info().project.name}`; await anmelden(page, "/einstellungen"); for (const alt of (await (await page.request.get("/api/v1/tokens")).json()) as Array<{ id: string; name: string; }>) { if (alt.name.startsWith(E2E_PRAEFIX)) await page.request.delete(`/api/v1/tokens/${alt.id}`); } await page.reload(); await page.locator("#token-name").fill(name); await page.getByRole("button", { name: "Token anlegen" }).click(); const anzeige = page.getByRole("status").filter({ hasText: "nur jetzt angezeigt" }); await expect(anzeige).toBeVisible(); const token = (await anzeige.locator("code").first().innerText()).trim(); expect(token).toMatch(/^pd_/); await keinQuerscrollen(page); const ich = await page.request.get("/api/v1/ich", { headers: { authorization: `Bearer ${token}`, cookie: "" }, }); expect(ich.status()).toBe(200); page.once("dialog", (d) => void d.accept()); await page.getByRole("button", { name: `Token ${name} widerrufen` }).click(); await expect(page.getByText(`${name} ·`)).toHaveCount(0); const danach = await page.request.get("/api/v1/ich", { headers: { authorization: `Bearer ${token}` } }); expect(danach.status()).toBe(401); });