Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e762bc2
test(integration): update auth-admin-bypasses-setup-gate for admin ro…
Gree44 Jul 25, 2026
a3d1705
test(integration): update admin-approve-template-version for admin ro…
Gree44 Jul 25, 2026
a14815f
test(integration): fix detail-delete-confirmation-flow for owner-only…
Gree44 Jul 25, 2026
265a90d
test(integration): reactivate auth-non-admin-cannot-access-admin-rout…
Gree44 Jul 25, 2026
9f681f5
docs(cypress): add delta test matrix for staging update
Gree44 Jul 25, 2026
0115898
test(cypress): add "student" to loginAs Role union
Gree44 Jul 25, 2026
e04c570
test(integration): repair detail-issue-207-ungrouped-members for curr…
Gree44 Jul 25, 2026
1ca72d3
test(integration): student-dashboard-lists-deployments
Gree44 Jul 25, 2026
134f4e1
test(integration): student-role-routing-guard
Gree44 Jul 25, 2026
60fec05
test(integration): student-deployment-details-credentials
Gree44 Jul 25, 2026
f35a9ed
test(integration): lecturer-management-lists
Gree44 Jul 25, 2026
13fb357
test(integration): lecturer-delete-cascade-poll
Gree44 Jul 25, 2026
b231c48
test(integration): admin-project-overview-renders
Gree44 Jul 25, 2026
93bc5cb
test(integration): admin-reject-template-with-reason
Gree44 Jul 25, 2026
ce5e739
test(integration): redeploy-deployment-config-override
Gree44 Jul 25, 2026
317482c
test(integration): course-filters-toggle-chip
Gree44 Jul 25, 2026
aac5a7d
test(integration): course-filters-admin-crud
Gree44 Jul 25, 2026
26fc782
test(integration): wizard-36-months-runtime
Gree44 Jul 25, 2026
fde35a6
test(integration): dashboard-expiry-color-tiers
Gree44 Jul 25, 2026
6d58f9f
test(integration): repair visual-capture spec for current page headings
Gree44 Jul 25, 2026
20477c3
test(cypress): tighten visual-capture overrides type to satisfy tsc
Gree44 Jul 25, 2026
d76347d
docs(cypress): add staging-update report (repairs + new feature tests)
Gree44 Jul 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions cypress/REPORT-STAGING-UPDATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Abschlussbericht — Cypress-Suite-Update auf staging-Stand (2026-07)

> Aktualisierung der bestehenden Cypress-Integrationstest-Suite an den neuen `origin/staging`-Stand (~7.800 geänderte `src/`-Zeilen seit der ursprünglichen Test-Baseline `d1af1a7`). Reparatur veralteter Tests + neue Feature-Abdeckung. Arbeitsstand: Worktree-Branch `integrationstests-staging-update`.

## Ergebnis

| Check | Ergebnis |
| --- | --- |
| **Cypress-Suite** | **46/46 Tests grün** über **30 Specs**, 0 failing (~61s) |
| **TypeScript** (`tsc --noEmit -p cypress/tsconfig.json`) | 0 Fehler |
| **ESLint** | 0 Fehler, 40 Warnings (alle pre-existing) |
| **Build** (`vite build`) | success |

## Zahlen

| Metrik | Wert |
| --- | --- |
| Commits in dieser Session | **21** |
| Reparierte bestehende Tests | **5** |
| Reaktivierte Tests (vorher `skip`) | **1** (Admin-Guard) |
| Neue Feature-Tests | **11** (2× P0, 7× P1, 2× P2) |
| Neue Fixtures | ~12 (Student, Lecturer, Admin-Deployments, Redeploy-Params, Course-Filter, …) |
| Produktivcode-Änderungen | **0** |
| Neue `data-testid` | **0** (alle Tests über role/text/id/aria-Selektoren) |

## Kontext des Updates

`origin/staging` hatte sich seit der ersten Suite massiv weiterentwickelt. Der aktuelle Checkout (`feature/192`) und die alte `integrationstests`-Arbeit waren bereits nach `staging` gemerged. Neu hinzugekommen: Student-Self-Service, Lecturer-Verwaltung (Admin), Admin-Rework (Split von `/admin`), Redeploy/VM-Config-Override, Kurs-Filter, 36-Monate-Laufzeit, Expiry-Farbstufen, Owner-Only-Gating, `ProtectedRoute`, `MobileTopBar`, `CredentialInstanceCard`.

## Teil A — Reparaturen (6)

| Test | Ursache | Fix | Commit |
| --- | --- | --- | --- |
| `auth-admin-bypasses-setup-gate` | `/admin` gesplittet | Ziel → `/admin/projects`, Heading „Projektübersicht" | e762bc2 |
| `admin-approve-template-version` | Queue nach `/admin/templates` verschoben | Route angepasst | a3d1705 |
| `detail-delete-confirmation-flow` | Owner-Only-Gate (fix/126) | `owner_id` in Fixture | a14815f |
| `auth-non-admin-cannot-access-admin-route` | war geskippt; Befund via `ProtectedRoute` behoben | neu geschrieben (Redirect-Assertion) + reaktiviert | 265a90d |
| `detail-issue-207-ungrouped-members` (fremd) | Card-Titel off-screen | `scrollIntoView()` | e04c570 |
| `visual-capture` (fremd) | Mobile: `cy.contains` traf versteckten Sidebar-Link; falscher Reference-Pfad | `cy.contains("h1", …)` + Pfad-Fix | 6d58f9f, 20477c3 |

## Teil B — Neue Feature-Tests (11)

### Student-Self-Service
- `student-dashboard-lists-deployments` **[P0]** (1ca72d3) — Routing `/` → `/student/dashboard` + Deployment-Karten
- `student-role-routing-guard` **[P0]** (134f4e1) — Student auf `/dashboard`/`/appstore` → Redirect; keine Lecturer-Calls
- `student-deployment-details-credentials` **[P1]** (60fec05) — VMs + Credentials (CredentialInstanceCard mode=student)

### Admin-Bereich
- `lecturer-management-lists` **[P1]** (f35a9ed) — Lecturer-Tabelle + Detail-Dialog
- `lecturer-delete-cascade-poll` **[P1]** (13fb357) — Type-to-confirm + DELETE 202 + Poll 200→404
- `admin-project-overview-renders` **[P1]** (b231c48) — Stat-Cards + View-Selector (Dozent/Kurs/Datum)
- `admin-reject-template-with-reason` **[P1]** (93bc5cb) — Reject-Flow mit Begründung im Payload

### Deployment / Courses
- `redeploy-deployment-config-override` **[P1]** (ce5e739) — RedeployDialog, nur geänderte Params im Override
- `course-filters-toggle-chip` **[P1]** (317482c) — client-seitige Chip-Filterung
- `course-filters-admin-crud` **[P1]** (aac5a7d) — Admin: Filter anlegen (POST) + löschen (DELETE)

### Wizard / Dashboard
- `wizard-36-months-runtime` **[P2]** (26fc782) — „3 Jahre"-Laufzeit → `runtime_months: 36`
- `dashboard-expiry-color-tiers` **[P2]** (fde35a6) — warning/critical/expired-Indikatoren (relative Daten)

## Infrastruktur-Anpassungen

- **`Role`-Union** um `"student"` erweitert (commands.ts + index.d.ts, Commit 0115898) — nutzt bestehende `keycloak/student.json`.
- **Delta-Matrix** `cypress/TEST-MATRIX-DELTA.md` (9f681f5) dokumentiert Reparaturen + neue Tests.
- **`cy.mockApi`** deckte bereits `template-versions/queue` + `openstack/flavors` ab (aus der ersten Session).

## Bestätigte Sicherheits-Verbesserung

Der ursprünglich in `SECURITY-FINDINGS.md` dokumentierte Privilege-Escalation-Befund (`/admin` ohne Frontend-Rollen-Gate) ist auf `staging` **behoben**: `ProtectedRoute requireAdmin` leitet Nicht-Admins auf `/dashboard` um. Der zuvor geskippte Test ist reaktiviert und sichert den Guard; das Finding wurde auf **RESOLVED** aktualisiert.

## Umgebungs-Hinweis (nicht committed)

Auf diesem Rechner (Apple M2, aber **x64-node unter Rosetta**) kollidiert Cypress' gebündeltes arm64-esbuild mit dem x64-node beim Kompilieren der TS-Config. Lokaler Workaround: `ESBUILD_BINARY_PATH` auf ein passendes x64-esbuild-0.28.0-Binary setzen — **nur für Cypress**, nicht für Vite (das braucht 0.25.12). Ein lokales Runner-Skript `/tmp/local-cypress-run.sh` kapselt das. **Nichts davon wurde committed** — auf einer arch-konsistenten Maschine / in CI ist der Workaround unnötig und würde dort sogar stören. `node_modules` im Worktree ist ein Symlink auf den Haupt-Checkout.

## Verbliebene Risiken / Nicht abgedeckt

- **`TemplateIconUpload`** (Multipart-Icon-Upload im AppStore-Owner-Dialog) — nicht getestet (Datei-Upload + Multipart-Intercept aufwändig).
- **SSH-Key-Download** im Student-Detail (Blob + Content-Disposition) — nur Anzeige getestet, nicht der Download.
- **`NoRolePage`** — laut Analyse ist `/no-role` nicht als Route registriert (nur ProtectedRoute-Redirect-Ziel), landet über Catch-all bei `/dashboard`. Potenzielle Lücke, nicht durch Test fixiert.
- **Rename-Flow** der Course-Filter (PATCH) — nur Create/Delete getestet.
- **Fehlerpfade** vieler neuer Features (403/404/409) — überwiegend Happy-Path abgedeckt, analog zur bestehenden P0/P1-Tiefe.
- **Redeploy pro VM** (Instance-Redeploy) — nur der Deployment-weite Redeploy getestet.

## Mögliche zukünftige Testfälle

1. `student-credentials-error-403/404` — Fehlerpfade der Credentials
2. `redeploy-instance-per-vm` — VM-einzelner Redeploy
3. `redeploy-only-owner-or-running` — Permission/Status-Gate des Redeploy-Buttons
4. `course-filters-rename` — PATCH-Flow
5. `course-filters-lecturer-readonly` — Lecturer sieht keine CRUD-Controls
6. `template-icon-upload` — Multipart-Upload + Preview
7. `lecturer-delete-timeout` — Poll läuft 30s ohne 404 → Timeout-State
8. `admin-project-overview-drilldown` — Klick in Dozent/Kurs-Gruppe → Detailtabelle
9. `mobile-topbar-drawer-nav` — Hamburger-Drawer-Navigation (Viewport-Test)
10. `wizard-extend-runtime` — Laufzeit-Verlängerung (PATCH /extend) auf der Detailseite

## Nächste Schritte

Branch `integrationstests-staging-update` ist **21 Commits vor `origin/staging`**. Nach Freigabe: `git push` + PR nach `staging`. Für CI müsste `npm run test:e2e` (via `start-server-and-test`) noch als Pipeline-Schritt ergänzt werden, damit die Suite auf jedem PR läuft — auf arch-konsistenten Runnern ohne den esbuild-Workaround.
12 changes: 8 additions & 4 deletions cypress/SECURITY-FINDINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@

**Severity**: High (privilege escalation, defence-in-depth)
**Discovered while writing**: `cypress/e2e/auth/auth-non-admin-cannot-access-admin-route.cy.ts`
**State**: Test is `describe.skip(...)` until the fix lands.
**State**: ✅ **RESOLVED on `staging`** (admin-area rework). The test is now un-skipped and active — it passes against the fixed code and guards against regression.

### Finding
### Resolution

`AdminMonitoring.tsx` has no role gate. `App.tsx` line 159 wires `<AdminMonitoring />` at `/admin` for any authenticated user. The Sidebar nav link IS gated (`Sidebar.tsx` line 121 checks `isAdmin`), but that is only a UI affordance, not a security boundary.
The admin-area rework replaced the single ungated `/admin` route with three routes — `/admin/projects`, `/admin/templates`, `/admin/lecturers` — each wrapped in `<ProtectedRoute requireAdmin>` (`src/components/ProtectedRoute.tsx`). For a non-admin, `requireAdmin && !isAdmin` returns `<Navigate to="/dashboard" replace>`, so `AdminTemplateApprovals` / `AdminProjectOverview` never mount and their data effects never run. `AdminMonitoring.tsx` (the old ungated page) is now dead code. The regression test asserts: lecturer → `/admin/templates` redirects to `/dashboard`, the "Template-Freigaben" H1 is absent, and the approval-queue fetch never fires.

A user authenticated with `roles=["lecturer"]` (no admin role) who navigates directly to `/admin` sees:
### Original finding (for the record)

`AdminMonitoring.tsx` had no role gate. `App.tsx` wired `<AdminMonitoring />` at `/admin` for any authenticated user. The Sidebar nav link IS gated (`Sidebar.tsx` checks `isAdmin`), but that is only a UI affordance, not a security boundary.

A user authenticated with `roles=["lecturer"]` (no admin role) who navigated directly to `/admin` saw:

- the full `Administration` heading and tabbed UI;
- `getAllDeployments(null)` is fired — returns the **global** deployment list (every lecturer's work), not just their own;
Expand Down
68 changes: 68 additions & 0 deletions cypress/TEST-MATRIX-DELTA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Cypress Delta Test Matrix — staging update (2026-07)

> Ergänzung zur ursprünglichen `TEST-MATRIX.md`. Erfasst (A) Reparaturen bestehender Tests nach dem großen staging-Update (~7.800 geänderte src-Zeilen) und (B) neue Feature-Tests. Konventionen wie zuvor: Keycloak gestubbt, alle API via `cy.intercept` + Fixtures, keine festen Waits, stabile Selektoren.

## Kontext des Updates

Zwischen der Test-Baseline (`d1af1a7`) und `origin/staging` kamen u.a. hinzu: Student-Self-Service, Lecturer-Verwaltung (Admin), Admin-Rework (Split von `/admin` in `/admin/projects|templates|lecturers` mit `ProtectedRoute`), Redeploy/VM-Config-Override, Kurs-Filter, 36-Monate-Laufzeit, Expiry-Farbstufen, Owner-Only-Gating für Deployment-Aktionen, `NoRolePage`, `MobileTopBar`, `TemplateIconUpload`, `CredentialInstanceCard`.

## Teil A — Reparaturen (erledigt)

| Test | Ursache der Regression | Fix | Status |
| --- | --- | --- | --- |
| `auth-admin-bypasses-setup-gate` | `/admin` existiert nicht mehr (Split) | Ziel-Route → `/admin/projects`, Heading → „Projektübersicht" | ✅ e762bc2 |
| `admin-approve-template-version` | Approval-Queue nach `AdminTemplateApprovals` (`/admin/templates`) verschoben | `cy.loginAs("admin","/admin/templates")` | ✅ a3d1705 |
| `detail-delete-confirmation-flow` | Owner-Only-Gate (`fix/126`): Delete-Button ohne `owner_id` disabled | `owner_id: user-lec-1` in Fixture | ✅ a14815f |
| `auth-non-admin-cannot-access-admin-route` | War geskippt (Befund); jetzt via `ProtectedRoute requireAdmin` behoben | Neu geschrieben: Redirect `/admin/templates`→`/dashboard`, reaktiviert | ✅ 265a90d |
| `detail-issue-207-ungrouped-members` (fremd) | CourseGroupsCard-Rendering/Selektor geändert | in Arbeit (Sub-Agent) | 🔧 |

## Teil B — Neue Feature-Tests (geplant, P0/P1-Tiefe wie Bestand)

```
Bereich
└── Funktion
└── Testfall [Prio · Typ] — Ziel · Risiko bei Regression
```

### student · self-service
- `student-dashboard-lists-deployments` **[P0 · success]** — Pure Student → `/student/dashboard`, `GET /api/v1/student/deployments` rendert Karten. Risiko: Studenten sehen ihre Umgebungen nicht.
- `student-dashboard-empty-state` **[P1 · edge]** — leere Liste → „Aktuell keine Deployments für dich verfügbar." Risiko: kaputter Erstkontakt.
- `student-role-routing-guard` **[P0 · permission]** — Pure Student auf `/dashboard`/`/appstore` → Redirect `/student/dashboard`. Risiko: Student sieht Lecturer-UI.
- `student-deployment-details-credentials` **[P1 · success]** — `/student/deployment/:id` lädt Credentials, `CredentialInstanceCard` (mode=student) rendert. Risiko: Zugangsdaten unerreichbar.

### admin · lecturer-management
- `lecturer-management-lists` **[P1 · success]** — `/admin/lecturers`, `GET /api/v1/lecturers` Tabelle. Risiko: Admin-Verwaltung tot.
- `lecturer-delete-cascade-poll` **[P1 · success]** — Detail-Dialog → „Account löschen" → Type-to-confirm → DELETE 202 → Poll 200→404 → Erfolg. Risiko: Cascade-Delete-Feedback bricht.

### admin · project-overview
- `admin-project-overview-renders` **[P1 · success]** — `/admin/projects`, Stat-Cards + View-Selector. Risiko: globale Übersicht kaputt.

### admin · template-approvals (reject)
- `admin-reject-template-with-reason` **[P1 · success]** — „Ablehnen" → Reason → POST `/reject` `{reason}`. Risiko: Autoren ohne Feedback.

### deployment · redeploy
- `redeploy-deployment-config-override` **[P1 · success]** — „Neu deployen" (running+owner) → Param ändern → POST `/redeploy` mit `deployment_parameter_overrides`+`preserve_credentials`. Risiko: Redeploy-Feature tot.
- `redeploy-only-owner-running` **[P1 · permission]** — Button disabled für Nicht-Owner / nicht-running. Risiko: unautorisierter Redeploy.

### courses · course-filters
- `course-filters-toggle-chip` **[P1 · state]** — Chip klicken filtert Kursliste client-seitig. Risiko: Filter unbenutzbar.
- `course-filters-admin-crud` **[P1 · success]** — Admin: Filter hinzufügen (POST) / löschen (DELETE). Risiko: Filterverwaltung kaputt.
- `course-filters-lecturer-readonly` **[P2 · permission]** — Lecturer sieht keine Add/Edit/Delete-Controls. Risiko: Nicht-Admin ändert Filter.

### wizard · runtime
- `wizard-36-months-runtime` **[P2 · edge]** — 36-Monate-Option („3 Jahre") wählbar, `runtime_months:36` im POST. Risiko: neue Laufzeit nicht buchbar.

### dashboard · expiry-tiers
- `dashboard-expiry-color-tiers` **[P2 · state]** — critical/warning/expired via dynamischen `expires_at` zeigen korrektes Icon. Risiko: verpasste Ablaufwarnungen.

### cross-cutting · mobile-nav
- `mobile-topbar-drawer-nav` **[P2 · state]** — `cy.viewport(375,667)` → Hamburger öffnet Sidebar-Drawer. Risiko: Mobile-Navigation kaputt.
```

## Priorisierung

- **P0**: student-dashboard-lists, student-role-routing-guard (Kern-Sicherheit + Kernpfad des neuen größten Bereichs)
- **P1**: student-details/-empty, lecturer-mgmt (2), admin-project-overview, admin-reject, redeploy (2), course-filters (2)
- **P2**: course-filters-readonly, 36-months, expiry-tiers, mobile-nav

Umfang orientiert sich am Bestand (P0/P1-Tiefe): kritische Happy-Paths + wichtigste Guards/Fehlerfälle pro Feature, keine erschöpfende Rollen-/Edge-Matrix.
22 changes: 12 additions & 10 deletions cypress/e2e/admin/admin-approve-template-version.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

// admin-approve-template-version
// ──────────────────────────────
// P0 success-path test: an admin on /admin sees the Template-Freigaben card
// populated with pending public template-versions (loaded via GET
// /api/v1/template-versions/queue). Clicking "Schnell genehmigen" on a row
// P0 success-path test: an admin on /admin/templates sees the Template-
// Freigaben card populated with pending public template-versions (loaded via
// GET /api/v1/template-versions/queue). Clicking "Schnell genehmigen" on a row
// must fire POST /api/v1/template-versions/<id>/approve and the row must
// disappear from the queue UI on success.
//
Expand All @@ -15,11 +15,13 @@
// versions pile up indefinitely and the AppStore stops growing — both real
// user-visible regressions.
//
// AdminMonitoring.tsx renders one card per pending TemplateVersionQueueItem
// (template name + version pill + "Schnell genehmigen" / "Details prüfen"
// buttons). handleApprove() calls approveTemplateVersion() from
// src/api/github.ts and then removes the version from local state. The
// per-row removal is the user-visible signal that the action succeeded.
// AdminTemplateApprovals.tsx (route /admin/templates, split out of the old
// AdminMonitoring.tsx during the admin rework) renders one card per pending
// TemplateVersionQueueItem (template name + version pill + "Schnell
// genehmigen" / "Ablehnen" buttons). handleApprove() calls
// approveTemplateVersion() from src/api/github.ts and then removes the
// version from local state. The per-row removal is the user-visible signal
// that the action succeeded.
//
// Fixture
// queue-pending.json mirrors the QueueResponse shape from
Expand Down Expand Up @@ -53,7 +55,7 @@ describe("Admin · approves a pending template-version from the queue", () => {
});

it("clicks 'Schnell genehmigen' on the first pending version and the row disappears", () => {
cy.loginAs("admin", "/admin");
cy.loginAs("admin", "/admin/templates");

// The queue load is the load-bearing fetch for the Template-Freigaben
// card. Waiting on it proves the GET fired AND that the response landed
Expand All @@ -77,7 +79,7 @@ describe("Admin · approves a pending template-version from the queue", () => {
}).as("getQueueEmpty");

// Click the "Schnell genehmigen" button scoped to the WordPress row.
// The button text is exact (AdminMonitoring.tsx line ~900). We scope to
// The button text is exact (AdminTemplateApprovals.tsx:350). We scope to
// the row by locating the template-name <h3> and walking up to the
// surrounding Card (`data-slot="card"`) — both pending rows render the
// same button text, so unscoped cy.contains would race between them.
Expand Down
Loading
Loading