diff --git a/docs/CRUD_CHAINING_GUIDE.md b/docs/CRUD_CHAINING_GUIDE.md new file mode 100644 index 0000000..e04d81a --- /dev/null +++ b/docs/CRUD_CHAINING_GUIDE.md @@ -0,0 +1,335 @@ +# 🔗 CRUD Testovací Řetězec v Bruno – Kompletní průvodce + +CRUD (Create, Read, Update, Delete) řetězení umožňuje **testovat celý životní cyklus** jednoho záznamu napříč více požadavky. V Bruno se to dělá pomocí **sdílených proměnných prostředí** a **post-response skriptů**. + +--- + +## 🎯 Princip řetězení v Bruno + +``` +POST /spells → Vytvoří kouzlo → Uloží ID do proměnné + ↓ +GET /spells/:id → Získá kouzlo → Ověří vytvoření + ↓ +PUT /spells/:id → Upraví kouzlo → Ověří úpravu + ↓ +DELETE /spells/:id → Smaže kouzlo → Ověří smazání + ↓ +GET /spells/:id → Získá kouzlo → Ověří 404 (smazáno) +``` + +**Klíčové prvky:** +- `bru.setEnvVar("variableName", value)` – Ukládá hodnotu do proměnných prostředí +- `{{variableName}}` – Používá uloženou hodnotu v dalších požadavcích +- **Pořadí (seq)** – Určuje, v jakém pořadí se testy spouštějí + +--- + +## 📝 Krok za krokem: CRUD pro /spells + +### Předpoklady +- Backend PotterAPI běží na `localhost:3000` +- Kolekce `PotterAPI` existuje + +--- + +### Krok 1: Vytvoř složku pro CRUD testy +```bash +mkdir -p apiTest/PotterAPI/CRUD +``` + +--- + +### Krok 2: Vytvoř folder.yml +```yaml +# apiTest/PotterAPI/CRUD/folder.yml +info: + name: CRUD - Spells + type: folder + seq: 10 + +request: + auth: inherit +``` + +--- + +### Krok 3: Vytvoř test pro CREATE (POST) +```yaml +# apiTest/PotterAPI/CRUD/1-CREATE-spell.yml +info: + name: CREATE - Create new spell + type: http + seq: 1 + +http: + method: POST + url: http://localhost:3000/spells + auth: inherit + headers: + - name: Content-Type + value: application/json + body: + raw: |- + { + "spell": "Lumos Maximus", + "type": "Charm", + "effect": "Creates bright light", + "isUnforgivable": false + } + +runtime: + scripts: + # Uloží ID nově vytvořeného kouzla + - type: post-response + code: |- + if (res.getStatus() === 201) { + const body = res.getBody(); + bru.setEnvVar("createdSpellId", body.spell.id); + bru.setEnvVar("createdSpellName", body.spell.spell); + } + + # Testy pro vytvoření + - type: tests + code: |- + test("Status code is 201 Created", function() { + expect(res.getStatus()).to.equal(201); + }); + + test("Response contains spell with id", function() { + const body = res.getBody(); + expect(body.spell).to.have.property("id"); + expect(body.spell.id).to.be.a("string"); + }); + +settings: + encodeUrl: true + timeout: 10000 +``` + +--- + +### Krok 4: Vytvoř test pro READ (GET) +```yaml +# apiTest/PotterAPI/CRUD/2-READ-spell.yml +info: + name: READ - Get created spell + type: http + seq: 2 + +http: + method: GET + url: http://localhost:3000/spells/{{createdSpellId}} + auth: inherit + +runtime: + scripts: + - type: tests + code: |- + test("Status code is 200 OK", function() { + expect(res.getStatus()).to.equal(200); + }); + + test("Response contains the created spell", function() { + const body = res.getBody(); + expect(body.spell).to.equal(bru.getEnvVar("createdSpellName")); + expect(body.id).to.equal(bru.getEnvVar("createdSpellId")); + }); + +settings: + encodeUrl: true + timeout: 10000 +``` + +--- + +### Krok 5: Vytvoř test pro UPDATE (PUT) +```yaml +# apiTest/PotterAPI/CRUD/3-UPDATE-spell.yml +info: + name: UPDATE - Update spell + type: http + seq: 3 + +http: + method: PUT + url: http://localhost:3000/spells/{{createdSpellId}} + auth: inherit + headers: + - name: Content-Type + value: application/json + body: + raw: |- + { + "spell": "Lumos Maximus Updated", + "type": "Charm", + "effect": "Creates very bright light", + "isUnforgivable": false + } + +runtime: + scripts: + - type: post-response + code: |- + if (res.getStatus() === 201) { + bru.setEnvVar("createdSpellName", "Lumos Maximus Updated"); + } + + - type: tests + code: |- + test("Status code is 201 Updated", function() { + expect(res.getStatus()).to.equal(201); + }); + + test("Response contains updated spell", function() { + const body = res.getBody(); + expect(body.spell).to.equal("Lumos Maximus Updated"); + }); + +settings: + encodeUrl: true + timeout: 10000 +``` + +--- + +### Krok 6: Vytvoř test pro DELETE +```yaml +# apiTest/PotterAPI/CRUD/4-DELETE-spell.yml +info: + name: DELETE - Delete spell + type: http + seq: 4 + +http: + method: DELETE + url: http://localhost:3000/spells/{{createdSpellId}} + auth: inherit + +runtime: + scripts: + - type: tests + code: |- + test("Status code is 200 OK", function() { + expect(res.getStatus()).to.equal(200); + }); + + test("Response contains delete message", function() { + const body = res.getBody(); + expect(body.message).to.equal("spell deleted"); + }); + +settings: + encodeUrl: true + timeout: 10000 +``` + +--- + +### Krok 7: Vytvoř test pro ověření smazání +```yaml +# apiTest/PotterAPI/CRUD/5-VERIFY-deleted.yml +info: + name: VERIFY - Check spell is deleted + type: http + seq: 5 + +http: + method: GET + url: http://localhost:3000/spells/{{createdSpellId}} + auth: inherit + +runtime: + scripts: + - type: tests + code: |- + test("Status code is 404 Not Found", function() { + expect(res.getStatus()).to.equal(404); + }); + +settings: + encodeUrl: true + timeout: 10000 +``` + +--- + +## 🚀 Jak to spustit + +### Spuštění celého CRUD řetězce +```bash +cd apiTest/PotterAPI +npx @usebruno/cli run CRUD -r +``` + +--- + +## 💡 Pokročilé techniky + +### 1. Více řetězců v jedné kolekci +Můžeš vytvořit několik nezávislých CRUD řetězců (např. pro různé typy kouzel). + +### 2. Sdílené proměnné napříč kolekcemi +Proměnné prostředí (`bru.setEnvVar`) jsou **globální** pro celou kolekci. + +### 3. Čištění po testu +Pro idempotentní testy (které lze opakovat): +```javascript +// V poslední fázi řetězce +bru.setEnvVar("createdSpellId", null); +bru.setEnvVar("createdSpellName", null); +``` + +--- + +## 📌 Kompletní příklad pro /characters + +**⚠️ Důležité:** `/characters` endpoint vyžaduje **Basic Auth**! + +```yaml +# 1-CREATE-character.yml +http: + method: POST + url: http://localhost:3000/characters + auth: inherit + headers: + - name: Content-Type + value: application/json + body: + raw: |- + { + "name": "Test Character", + "role": "student", + "house": "Gryffindor", + "school": "Hogwarts" + } + +runtime: + scripts: + - type: post-response + code: |- + if (res.getStatus() === 201) { + const body = res.getBody(); + bru.setEnvVar("createdCharacterId", body.character.id); + } +``` + +--- + +## 🔧 Řešení problémů + +| Problém | Příčina | Řešení | +|---------|---------|--------| +| Proměnná je `undefined` | Špatné pořadí `seq` | Zkontroluj, že CREATE má `seq: 1` | +| 404 na CREATE | Špatná URL | Zkontroluj, že URL je `http://localhost:3000/spells` | +| 400 na CREATE | Duplicitní data | Použij unikátní názvy | +| Testy selhávají | Backend neběží | Spusť `npm run start:potter-api` | +| "You can run only..." | Špatný adresář | Spusť z `apiTest/PotterAPI/` | + +--- + +## 📚 Dokumentace + +- [Bruno - Environment Variables](https://www.usebruno.com/docs/collections/environments) +- [Bruno - Scripting](https://www.usebruno.com/docs/scripting) +- [Chai Assertions](https://www.chaijs.com/api/bdd/) \ No newline at end of file