====== LU08.A03 - Bookshelf mit Authentifikation mit Bruno ====== Erweitere deine Bruno-Requests um Authentifikation, Autorisation und automatisierte Tests – nach Bruno-Best-Practices. ===== Zielbild ===== * **Ordnerstruktur** pro Fachgebiet (''auth'', ''books''). * **Kurz & klar benannte Requests** (kebab-case): ''login-admin.bru'', ''read-book.bru'', ''list-books.bru'' … * **Environments** je Zielsystem/Rolle statt Test-Steuerlogik im Script. * **Token** nur **temporär** in einem **lokalen** Environment, das **nicht** eingecheckt wird. * **Tests** sind klein, deterministisch, ohne Ablaufsteuerung. ===== Ausgangslage ===== Erweiterte API: ''https://it.bzz.ch/book/ext/'' (Login/Token, Rollen) ^ Rolle ^ Benutzername ^ Passwort ^ Berechtigung ^ | admin | admin | admin | alle Funktionen | | user | musterh | geheim | ''read'', ''list'' | | guest | – | – | nur ''login'' | Ohne gültiges Token → Rolle ''guest'' → nur ''login''. Fehlende Berechtigung → **403**. ===== Projektstruktur (Beispiel) ===== bookshelf-bruno/ auth/ login-admin.bru login-user.bru login-invalid.bru books/ read-book.bru list-books.bru save-book.bru (optional) delete-book.bru (optional) environments/ dev.json dev.local.json (in .gitignore!) **Hinweise** * ''dev.json'': allgemeine, unverfängliche Variablen (''baseUrl''). * ''dev.local.json'': **Token** & sensible Werte (nicht ins Repo). * In Bruno das passende **Environment aktivieren** (oben ''Environment''). ===== Environments ===== **environments/dev.json** { "name": "dev", "vars": { "baseUrl": "https://it.bzz.ch/book/ext" } } **environments/dev.local.json** (nicht einchecken) { "name": "dev.local", "vars": { "token": "", "username": "admin", "password": "admin" } } > Aktiviere zuerst ''dev'', dann zusätzlich ''dev.local'' (oder führe beide Inhalte zu einem aktiven Environment zusammen – Hauptsache: ''token'' lebt **lokal**). ===== Requests (auth) ===== === login-admin.bru === POST → ''%%{{baseUrl}}%%/login'' Body (JSON): ''{"username":"admin","password":"admin"}'' **Tests** test("Login admin -> 200 & token gesetzt", () => { expect(res.status).to.equal(200); const data = res.json(); expect(data).to.have.property("token"); // Token nur im aktiven (lokalen) Environment halten: bru.setEnvVar("token", data.token, { persist: false }); }); === login-user.bru === POST → ''%%{{baseUrl}}%%/login'' Body (JSON): ''{"username":"musterh","password":"geheim"}'' **Tests** test("Login user -> 200 & token gesetzt", () => { expect(res.status).to.equal(200); const data = res.json(); bru.setEnvVar("token", data.token, { persist: false }); }); === login-invalid.bru === POST → ''%%{{baseUrl}}%%/login'' Body (JSON): ''{"username":"wrong","password":"wrong"}'' **Tests** test("Login invalid -> 401, kein token", () => { expect(res.status).to.equal(401); }); ===== Requests (books) ===== **Gemeinsame Header**: im Request ''Headers'' * Key: ''Authorization'' * Value: ''Bearer %%{{token}}%%'' === read-book.bru === GET → ''%%{{baseUrl}}%%/read/'' **Tests (rollenbewusst, aber ohne Ablaufsteuerung)** test("Read Book – rollenabhängig", () => { const s = res.status; if (bru.getVar("token")) { // Angemeldet: admin/user expect(s).to.equal(200); const b = res.json(); expect(b).to.have.property("book_uuid"); expect(b).to.have.property("title"); } else { // guest (ohne Token) expect(s).to.equal(403); } }); === list-books.bru === GET → ''%%{{baseUrl}}%%/list'' **Tests** test("List Books – rollenabhängig", () => { const s = res.status; if (bru.getVar("token")) { expect(s).to.equal(200); const arr = res.json(); expect(Array.isArray(arr)).to.equal(true); // ggf. bekannte Länge/Felder prüfen } else { expect(s).to.equal(403); } }); ===== Ausführung (Runner ohne Script-Steuerlogik) ===== **Variante 1 (empfohlen & simpel): drei Läufe mit klaren Vorbedingungen** 1. ''guest'': ''token'' in ''dev.local.json'' leer lassen → ''list-books'', ''read-book'' ausführen → **403** erwartet. 2. ''user'': ''login-user.bru'' einmal senden → ''token'' wird gesetzt → ''list/read'' → **200**. 3. ''admin'': ''login-admin.bru'' einmal senden → ''list/read'' → **200** (volle Rechte). **Variante 2 (Runner-Ordnung):** * Ordner ''auth'' vor ''books'' ausführen (''login-*.bru'' zuerst), danach ''books/*''. > **Best Practice:** Keine Ablaufsteuerung per Script (''setNextRequest''). Halte Tests kurz & deterministisch; die **Reihenfolge** steuert der **Runner/Ordner**. ===== Qualitäts-Checks (Best Practices) ===== * **DRY**: Gemeinsame Header (''Authorization'') in den jeweiligen Requests; Wiederholungen minimieren. * **Kleine Assertions**: Je Testfile 1–3 präzise Assertions (Status, Struktur, zentrale Felder). * **Keine Persistenz für Tokens**: ''persist: false'' verwenden; ''dev.local.json'' nicht committen. * **Sprechende Namen**: Dateien/Ordner klar benennen (''login-admin'', ''list-books''). * **Reproduzierbar**: Jeder Request ist alleinstehend ausführbar (kein versteckter Zustand in Tests). ===== Abgabe ===== Packe dein Bruno-Projekt (Ordner mit allen ''.bru''-Dateien und den **nicht-sensiblen** Environments) als **ZIP** und lade die **ZIP-Datei** in Moodle hoch. **Nicht** einchecken/abgeben: Dateien mit Token/Passwörtern (z. B. ''dev.local.json''). ---- {{tag>M450-LU08}} [[https://creativecommons.org/licenses/by-nc-sa/4.0/|{{https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png}}]] Kevin Maurizi