- 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>
80 lines
3.8 KiB
TypeScript
80 lines
3.8 KiB
TypeScript
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);
|
||
});
|