Kassenbuch, Mitgliedsbeitraege, Rechnungen an Dritte, Kassenpruefung und die Auswertungen fuer die Jahreshauptversammlung. Die drei Festlegungen aus Phase-9.md sitzen in der Datenbank, nicht im Router: eine festgeschriebene Buchung ist unveraenderbar, ein geprueftes Jahr nimmt kein Datum mehr an, und gerechnet wird mit Prisma.Decimal. Korrigiert wird ueber eine Gegenbuchung mit Verweis - beide Buchungen bleiben stehen, damit im Kassenbuch zu sehen ist, dass etwas berichtigt wurde. Neun Modelle, drei Trigger, zwoelf CHECKs, sechs Router, fuenf Ansichten. Dazu der Bankimport fuer CAMT.053 und MT940 ohne Fremdbibliothek, der Kategorievorschlag aus dem eigenen Buchungsbestand und die Bruecke von Phase 8: aus einem gegengezeichneten Beleg wird eine Buchung, das Foto haengt danach an ihr. Nebenbei behoben: die Rechnungsnummer war ein Wettlauf, und eine Organisation liess sich nicht mehr loeschen, sobald sie eine festgeschriebene Buchung hatte - der Trigger kannte den Aufbewahrungsschalter nicht. 562 Unit-Tests und 239 E2E-Tests gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
128 lines
4.5 KiB
TypeScript
128 lines
4.5 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { ADMIN_EMAIL, ADMIN_PASSWORT, WEHR_NAME } from "./wehr";
|
|
|
|
/**
|
|
* Beispiel-E2E-Test für Phase 0.
|
|
*
|
|
* Geprüft wird der Weg, ohne den nichts anderes funktioniert: jemand ruft die
|
|
* Anwendung auf, wird zur Anmeldung geschickt, meldet sich an und landet auf
|
|
* der Übersicht seiner Wehr.
|
|
*
|
|
* Voraussetzung: Stack läuft, Datenbank ist migriert und geseedet.
|
|
* docker compose up -d && npm run db:migrate && npm run db:seed
|
|
*/
|
|
|
|
test.describe("Anmeldung", () => {
|
|
test("schickt nicht angemeldete Aufrufe zur Anmeldeseite", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await expect(page).toHaveURL(/\/anmelden/);
|
|
await expect(page.getByRole("heading", { name: "FLORIAN" })).toBeVisible();
|
|
});
|
|
|
|
test("weist eine falsche Kombination ab und nennt den Grund auf Deutsch", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/anmelden");
|
|
|
|
await page.getByLabel("E-Mail-Adresse").fill(ADMIN_EMAIL);
|
|
await page.getByLabel("Passwort").fill("definitiv-das-falsche-passwort");
|
|
await page.getByRole("button", { name: "Anmelden" }).click();
|
|
|
|
// Nicht ueber die Rolle "alert" suchen: Next.js haengt einen eigenen
|
|
// Routen-Ansager mit derselben Rolle in die Seite.
|
|
const meldung = page.getByTestId("anmeldefehler");
|
|
await expect(meldung).toBeVisible();
|
|
await expect(meldung).toContainText("stimmt nicht");
|
|
// Und niemand ist hineingekommen.
|
|
await expect(page).toHaveURL(/\/anmelden/);
|
|
});
|
|
|
|
test("lässt den Administrator hinein und zeigt die Übersicht seiner Wehr", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/anmelden");
|
|
|
|
await page.getByLabel("E-Mail-Adresse").fill(ADMIN_EMAIL);
|
|
await page.getByLabel("Passwort").fill(ADMIN_PASSWORT);
|
|
await page.getByRole("button", { name: "Anmelden" }).click();
|
|
|
|
await page.waitForURL("/");
|
|
|
|
// Die Wehr, gegen die getestet wird — nicht die Beispielwehr aus der
|
|
// Anleitung. Welche das ist, steht in wehr.ts.
|
|
await expect(page.getByRole("heading", { name: WEHR_NAME })).toBeVisible();
|
|
|
|
// Die Kennzahlen aus dem Seed: 15 Mitglieder, 3 Fahrzeuge, 40 Geräte.
|
|
await expect(page.getByText("Aktive Mitglieder")).toBeVisible();
|
|
await expect(
|
|
page.getByText("Fahrzeuge", { exact: true }).first(),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByText("Geräte", { exact: true }).first(),
|
|
).toBeVisible();
|
|
|
|
// Der Sicherheitshinweis muss stehen, solange nichts freigegeben ist.
|
|
await expect(page.getByRole("note")).toContainText("analoge Überwachung");
|
|
});
|
|
|
|
test("zeigt die Navigation mit allen umgesetzten Modulen", async ({ page }) => {
|
|
await page.goto("/anmelden");
|
|
await page.getByLabel("E-Mail-Adresse").fill(ADMIN_EMAIL);
|
|
await page.getByLabel("Passwort").fill(ADMIN_PASSWORT);
|
|
await page.getByRole("button", { name: "Anmelden" }).click();
|
|
await page.waitForURL("/");
|
|
|
|
const navigation = page.getByRole("navigation", {
|
|
name: "Hauptnavigation",
|
|
});
|
|
await expect(
|
|
navigation.getByRole("link", { name: "Übersicht" }),
|
|
).toBeVisible();
|
|
|
|
// Termine, Dienstausweis und Benachrichtigungen stehen seit Phase 7 zur
|
|
// Verfügung und sind Verweise.
|
|
await expect(
|
|
navigation.getByRole("link", { name: "Termine" }),
|
|
).toBeVisible();
|
|
await expect(
|
|
navigation.getByRole("link", { name: "Dienstausweis" }),
|
|
).toBeVisible();
|
|
await expect(
|
|
navigation.getByRole("link", { name: "Benachrichtigungen" }),
|
|
).toBeVisible();
|
|
|
|
// Die Kasse ist seit Phase 9 ein Verweis.
|
|
//
|
|
// Bis dahin stand hier die Gegenprobe: der Eintrag sichtbar, aber
|
|
// ausgegraut, weil das Modul noch fehlte. Mit Phase 9 gibt es kein
|
|
// künftiges Modul mehr — eine Zusicherung über ein Modul, das es nicht
|
|
// gibt, wäre keine. Kommt später eines dazu, gehört sie hierher zurück.
|
|
await expect(navigation.getByRole("link", { name: "Kasse" })).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Erreichbarkeit", () => {
|
|
test("meldet den Zustand der Randsysteme unter /api/health", async ({
|
|
request,
|
|
}) => {
|
|
const antwort = await request.get("/api/health");
|
|
expect(antwort.status()).toBe(200);
|
|
|
|
const daten = (await antwort.json()) as {
|
|
status: string;
|
|
dienste: {
|
|
datenbank: boolean;
|
|
speicher: boolean;
|
|
warteschlange: boolean;
|
|
};
|
|
};
|
|
|
|
expect(daten.status).toBe("ok");
|
|
expect(daten.dienste.datenbank).toBe(true);
|
|
expect(daten.dienste.speicher).toBe(true);
|
|
expect(daten.dienste.warteschlange).toBe(true);
|
|
});
|
|
});
|