====== 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