Files
proxdeploy/tests/e2e/einstellungen.spec.ts
T
florian.wildeandClaude Opus 5.5 9fdced62db feat(api): API-Tokens, kurze Anfragen und OpenAPI für die CLI
- ApiToken (nur SHA-256), Bearer-Anmeldung, POST /auth/token für proxdeploy login, Verwaltung in den Einstellungen
- anfrageErgaenzen: Storage, Bridge, Node, Benutzer und Schlüssel aus Server-Standards, Verbindungstest und Anmeldung – auch beim Template-Bau
- Abschlussbox serverseitig (abschluss), Web und CLI zeigen dieselben Zeilen
- Antwortschemas als zod, daraus /api/v1/openapi.json und cli/api/openapi.json (pnpm openapi)

Refs #013

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:08:27 +02:00

80 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});