mirror of
https://github.com/LukasKanka/api_test.git
synced 2026-11-11 06:34:36 +01:00
updateProject
This commit is contained in:
1 parent
c8118d2154
commit
31a7414f20
5 files changed
+426
-19
No files matched your search
@@ -5,17 +5,23 @@ info:
|
||||
|
||||
http:
|
||||
method: POST
|
||||
url: http://localhost:3000/spells
|
||||
url: http://localhost:3000/characters
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
data: |
|
||||
{
|
||||
"spell":"Fire speed",
|
||||
"effect":"fire speed forever",
|
||||
"type":"Curse",
|
||||
"isUnforgivable":"false"
|
||||
"name": "Lu Kankys",
|
||||
"role": "student",
|
||||
"house": "Gryffindor",
|
||||
"school": "Hogwarts School of Witchcraft and Wizardry",
|
||||
"dumbledoresArmy": true,
|
||||
"bloodStatus": "half-blood",
|
||||
"species": "human"
|
||||
}
|
||||
auth: inherit
|
||||
auth:
|
||||
type: basic
|
||||
username: admin
|
||||
password: supersecret
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
# 🧪 PotterAPI – Testovací scénáře, tipy a rady pro Bruno API Client
|
||||
|
||||
Tento dokument obsahuje přehled testovacích scénářů, tipů, triků a automatizovaných testů v aplikaci **Bruno** pro testování REST API **PotterAPI**.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 1. Nastavení Bruno (Environment & Kolekce)
|
||||
|
||||
### Vytvoření prostředí (Environment)
|
||||
V aplikaci Bruno si vytvořte nové prostředí (např. `Local`) s následujícími proměnnými:
|
||||
|
||||
| Název proměnné | Hodnota | Popis |
|
||||
| :--- | :--- | :--- |
|
||||
| `baseUrl` | `http://localhost:3000` | Základní URL lokálního serveru |
|
||||
| `username` | `****` | Přihlašovací jméno pro Basic Auth |
|
||||
| `password` | `*****` | Heslo pro Basic Auth |
|
||||
| `createdSpellId` | *(dynamicky)* | Ukládá ID nově vytvořeného kouzla |
|
||||
| `createdCharacterId` | *(dynamicky)* | Ukládá ID nově vytvořené postavy |
|
||||
|
||||
> [!TIP]
|
||||
> V Bruno můžete v URL i v těle požadavku používat proměnné pomocí zápisu `{{baseUrl}}/spells`.
|
||||
|
||||
---
|
||||
|
||||
## 🔑 2. Autentizace v Bruno
|
||||
|
||||
Aplikace používá dva druhy autentizace:
|
||||
|
||||
1. **Basic Auth (`****` / `*****`)**:
|
||||
- Vyžadováno pro **`/characters`** a **`/login`**.
|
||||
- **Nastavení v Bruno**: Na záložce **Auth** zvolte `Basic Auth` a zadejte `Username: {{username}}` a `Password: {{password}}`.
|
||||
|
||||
2. **Content-Type Header Check (`application/json`)**:
|
||||
- Vyžadováno pro `POST` a `PUT` u **`/spells`** (middleware `checkHeader`).
|
||||
- **Nastavení v Bruno**: Na záložce **Headers** přidejte `Content-Type: application/json`.
|
||||
|
||||
---
|
||||
|
||||
## 📋 3. Přehled testovacích scénářů
|
||||
|
||||
### 🪄 A. Kouzla (`/spells`)
|
||||
|
||||
#### 🟢 Pozitivní scénáře (Happy Path)
|
||||
1. **Získání všech kouzel**
|
||||
- **Metoda:** `GET {{baseUrl}}/spells`
|
||||
- **Očekávaný stav:** `200 OK`
|
||||
- **Kontrola:** Vrátí pole objektů, kde každý objekt má `id`, `spell`, `type`, `effect`, `isUnforgivable`.
|
||||
|
||||
2. **Filtrování kouzel podle typu**
|
||||
- **Metoda:** `GET {{baseUrl}}/spells?type=Curse`
|
||||
- **Očekávaný stav:** `200 OK`
|
||||
- **Kontrola:** Každá položka v poli má `"type": "Curse"`.
|
||||
|
||||
3. **Vytvoření nového kouzla**
|
||||
- **Metoda:** `POST {{baseUrl}}/spells`
|
||||
- **Headers:** `Content-Type: application/json`
|
||||
- **Body (JSON):**
|
||||
```json
|
||||
{
|
||||
"spell": "Expelliarmus",
|
||||
"type": "Charm",
|
||||
"effect": "Disarms your opponent",
|
||||
"isUnforgivable": "false"
|
||||
}
|
||||
```
|
||||
- **Očekávaný stav:** `201 Created`
|
||||
- **Kontrola:** Vrátí `{ message: "Spell created", spell: { id: "..." } }`. Hodnota `isUnforgivable` v `spells.json` bude `false` (boolean).
|
||||
|
||||
4. **Získání konkrétního kouzla podle ID**
|
||||
- **Metoda:** `GET {{baseUrl}}/spells/{{createdSpellId}}`
|
||||
- **Očekávaný stav:** `200 OK`
|
||||
- **Kontrola:** Vrátí objekt kouzla s odpovídajícím ID.
|
||||
|
||||
5. **Úprava kouzla**
|
||||
- **Metoda:** `PUT {{baseUrl}}/spells/{{createdSpellId}}`
|
||||
- **Headers:** `Content-Type: application/json`
|
||||
- **Body (JSON):**
|
||||
```json
|
||||
{
|
||||
"spell": "Expelliarmus Maxima",
|
||||
"type": "Charm",
|
||||
"effect": "Strongly disarms opponent",
|
||||
"isUnforgivable": false
|
||||
}
|
||||
```
|
||||
- **Očekávaný stav:** `201 Created`
|
||||
|
||||
6. **Smazání kouzla podle ID**
|
||||
- **Metoda:** `DELETE {{baseUrl}}/spells/{{createdSpellId}}`
|
||||
- **Očekávaný stav:** `200 OK`
|
||||
- **Kontrola:** Vrátí `{ message: "spell deleted" }`.
|
||||
|
||||
---
|
||||
|
||||
#### 🔴 Negativní a Hraniční scénáře
|
||||
1. **Chybějící hlavička Content-Type (POST / PUT)**
|
||||
- **Metoda:** `POST {{baseUrl}}/spells` (bez `Content-Type: application/json`)
|
||||
- **Očekávaný stav:** `400 Bad Request`
|
||||
- **Očekávaná zpráva:** `"Sorry Nick you dont have HEADer"`
|
||||
|
||||
2. **Pokus o vytvoření duplicitního kouzla**
|
||||
- **Metoda:** `POST {{baseUrl}}/spells` s národem kouzla, které již existuje (např. `"spell": "Alohomora"`)
|
||||
- **Očekávaný stav:** `400 Bad Request`
|
||||
- **Očekávaná zpráva:** `"Spell Alohomora already exists"`
|
||||
|
||||
3. **Neplatná data (Joi validation failure)**
|
||||
- **Metoda:** `POST {{baseUrl}}/spells`
|
||||
- **Body (JSON):** (příliš krátký název, neplatný typ)
|
||||
```json
|
||||
{
|
||||
"spell": "Ab",
|
||||
"type": "SuperPower",
|
||||
"isUnforgivable": true
|
||||
}
|
||||
```
|
||||
- **Očekávaný stav:** `422 Unprocessable Entity`
|
||||
|
||||
4. **Vyhledání neexistujícího kouzla**
|
||||
- **Metoda:** `GET {{baseUrl}}/spells/non-existing-id-999`
|
||||
- **Očekávaný stav:** `404 Not Found`
|
||||
|
||||
---
|
||||
|
||||
### 🧙 B. Postavy (`/characters`)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Všechny požadavky na `/characters` vyžadují **Basic Auth** (`****` / `*****`).
|
||||
|
||||
#### 🟢 Pozitivní scénáře
|
||||
1. **Získání všech postav**
|
||||
- **Metoda:** `GET {{baseUrl}}/characters`
|
||||
- **Auth:** Basic Auth
|
||||
- **Očekávaný stav:** `200 OK`
|
||||
|
||||
2. **Filtrování postav podle koleje a role**
|
||||
- **Metoda:** `GET {{baseUrl}}/characters?house=Gryffindor&role=student`
|
||||
- **Auth:** Basic Auth
|
||||
- **Očekávaný stav:** `200 OK`
|
||||
|
||||
3. **Vytvoření nové postavy**
|
||||
- **Metoda:** `POST {{baseUrl}}/characters`
|
||||
- **Auth:** Basic Auth
|
||||
- **Headers:** `Content-Type: application/json`
|
||||
- **Body (JSON):**
|
||||
```json
|
||||
{
|
||||
"name": "Hermione Granger",
|
||||
"role": "student",
|
||||
"house": "Gryffindor",
|
||||
"school": "Hogwarts School of Witchcraft and Wizardry",
|
||||
"dumbledoresArmy": true,
|
||||
"bloodStatus": "muggle-born",
|
||||
"species": "human"
|
||||
}
|
||||
```
|
||||
- **Očekávaný stav:** `201 Created`
|
||||
|
||||
4. **Smazání postavy podle ID**
|
||||
- **Metoda:** `DELETE {{baseUrl}}/characters/{{createdCharacterId}}`
|
||||
- **Auth:** Basic Auth
|
||||
- **Očekávaný stav:** `200 OK`
|
||||
|
||||
---
|
||||
|
||||
#### 🔴 Negativní scénáře
|
||||
1. **Přístup bez přihlášení (Neplatné/Chybějící Basic Auth)**
|
||||
- **Metoda:** `GET {{baseUrl}}/characters` (bez Auth)
|
||||
- **Očekávaný stav:** `401 Unauthorized`
|
||||
- **Očekávané tělo:** `{ "message": "Sorry Wizard, can't let you in." }`
|
||||
|
||||
2. **Duplicitní jméno postavy**
|
||||
- **Metoda:** `POST {{baseUrl}}/characters` se jménem, které již v databázi je (např. `"name": "Hannah Abbott"`)
|
||||
- **Očekávaný stav:** `400 Bad Request`
|
||||
|
||||
---
|
||||
|
||||
### 🏰 C. Koleje, Moudrý klobouk & Citáty (`/houses`, `/sortingHat`, `/quote`)
|
||||
|
||||
1. **Seznam kolejí**: `GET {{baseUrl}}/houses` -> `200 OK` (Vrátí 4 koleje)
|
||||
2. **Detail koleje**: `GET {{baseUrl}}/houses/5a05e2b252f039152b96b5a7` -> `200 OK` (Gryffindor)
|
||||
3. **Moudrý klobouk**: `GET {{baseUrl}}/sortingHat` -> `200 OK` (Vrátí náhodnou kolej)
|
||||
4. **Náhodný citát**: `GET {{baseUrl}}/quote` -> `200 OK` (Vrátí citát)
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 4. Testovací skripty & Asertace v Bruno
|
||||
|
||||
V Bruno můžete psát testy v záložce **Tests** (používá knihovnu Chai / JavaScript).
|
||||
|
||||
### Příklady asertací:
|
||||
|
||||
#### Test 1: Kontrola stavového kódu a struktury odpovědi při vytvoření kouzla
|
||||
*(Záložka Tests u requestu `POST /spells`)*:
|
||||
|
||||
```javascript
|
||||
test("Status code is 201 Created", function() {
|
||||
expect(res.getStatus()).to.equal(201);
|
||||
});
|
||||
|
||||
test("Response contains success message and spell ID", function() {
|
||||
const data = res.getBody();
|
||||
expect(data).to.have.property("message", "Spell created");
|
||||
expect(data.spell).to.have.property("id");
|
||||
});
|
||||
```
|
||||
|
||||
#### Test 2: Uložení vytvořeného ID do proměnné prostředí (Post-response Script)
|
||||
*(Záložka Script -> Post Response)*:
|
||||
|
||||
```javascript
|
||||
if (res.getStatus() === 201) {
|
||||
const body = res.getBody();
|
||||
bru.setEnvVar("createdSpellId", body.spell.id);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 5. Řetězení testů (Workflow v Bruno)
|
||||
|
||||
Díky skriptování můžete vytvořit automatizovaný řetězec testů:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. POST /spells<br/>(Vytvořit kouzlo)"] -->|"Uloží ID do env"| B["2. GET /spells/:id<br/>(Ověřit vytvoření)"]
|
||||
B --> C["3. PUT /spells/:id<br/>(Upravit kouzlo)"]
|
||||
C --> D["4. DELETE /spells/:id<br/>(Smazat kouzlo)"]
|
||||
D --> E["5. GET /spells/:id<br/>(Ověřit 404)"]
|
||||
```
|
||||
|
||||
1. **Krok 1 (`POST /spells`)**: Vytvoří kouzlo a v `Post Response` skriptu uloží `createdSpellId`.
|
||||
2. **Krok 2 (`GET /spells/{{createdSpellId}}`)**: Ověří, že kouzlo existuje.
|
||||
3. **Krok 3 (`PUT /spells/{{createdSpellId}}`)**: Upraví vlastnosti kouzla.
|
||||
4. **Krok 4 (`DELETE /spells/{{createdSpellId}}`)**: Smaže kouzlo.
|
||||
5. **Krok 5 (`GET /spells/{{createdSpellId}}`)**: Ověří navrácení kódů `404 Not Found`.
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tipy a doporučení pro Bruno
|
||||
|
||||
1. **Používejte Bruno Runner**:
|
||||
- V levém panelu můžete kliknout na menu kolekce a zvolit **Run**. Bruno spustí všechny testy v kolekci za sebou a zobrazí zelené/červené výsledky.
|
||||
2. **Ukládání kolekce do Gitu**:
|
||||
- Bruno ukládá kolekce jako čisté `.bru` soubory přímo ve vaší složce projektu. Můžete je verzovat v Gitu spolu s kódem backendu!
|
||||
3. **Reset databáze před testy**:
|
||||
- Na začátku testovací sady doporučujeme zavolat `GET {{baseUrl}}/spells/actions/reset`, aby byl stav `spells.json` vždy předvídatelný.
|
||||
@@ -2390,5 +2390,18 @@
|
||||
"deathEater": false,
|
||||
"bloodStatus": "unknown",
|
||||
"species": "house-elf"
|
||||
},
|
||||
{
|
||||
"id": "51mzpz80m6x5u1zb8kogw4xn",
|
||||
"name": "Lu Kankys",
|
||||
"role": "student",
|
||||
"house": "Gryffindor",
|
||||
"school": "Hogwarts School of Witchcraft and Wizardry",
|
||||
"dumbledoresArmy": true,
|
||||
"bloodStatus": "half-blood",
|
||||
"species": "human",
|
||||
"ministryOfMagic": false,
|
||||
"orderOfThePhoenix": false,
|
||||
"deathEater": false
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,145 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const charactersFilePath = path.join(__dirname, '../characters.json');
|
||||
|
||||
var characters = require('../characters.json');
|
||||
const joi = require('joi');
|
||||
const initialCharacters = JSON.parse(JSON.stringify(require('../characters.json')));
|
||||
const randomId = require('random-id');
|
||||
|
||||
const schema = joi.object().keys({
|
||||
id: joi.any(),
|
||||
name: joi.string().min(2).max(100).required(),
|
||||
role: joi.string().allow('', null),
|
||||
house: joi.string().allow('', null),
|
||||
school: joi.string().allow('', null),
|
||||
ministryOfMagic: joi.boolean().default(false),
|
||||
orderOfThePhoenix: joi.boolean().default(false),
|
||||
dumbledoresArmy: joi.boolean().default(false),
|
||||
deathEater: joi.boolean().default(false),
|
||||
bloodStatus: joi.string().allow('', null),
|
||||
species: joi.string().allow('', null),
|
||||
wand: joi.string().allow('', null),
|
||||
boggart: joi.string().allow('', null),
|
||||
patronus: joi.string().allow('', null),
|
||||
animagus: joi.string().allow('', null),
|
||||
alias: joi.string().allow('', null)
|
||||
});
|
||||
|
||||
function saveCharactersToFile() {
|
||||
fs.writeFileSync(charactersFilePath, JSON.stringify(characters, null, 4));
|
||||
}
|
||||
|
||||
exports.characters_list = function (req, res) {
|
||||
let result = characters;
|
||||
if (typeof req.query.house !== 'undefined') {
|
||||
result = result.filter(c => c.house === req.query.house);
|
||||
}
|
||||
if (typeof req.query.role !== 'undefined') {
|
||||
result = result.filter(c => c.role === req.query.role);
|
||||
}
|
||||
if (typeof req.query.bloodStatus !== 'undefined') {
|
||||
result = result.filter(c => c.bloodStatus === req.query.bloodStatus);
|
||||
}
|
||||
if (typeof req.query.species !== 'undefined') {
|
||||
result = result.filter(c => c.species === req.query.species);
|
||||
}
|
||||
if (typeof req.query.limit !== 'undefined') {
|
||||
result = result.slice(0, req.query.limit);
|
||||
}
|
||||
|
||||
return res.json(result);
|
||||
};
|
||||
|
||||
exports.specific_character = function (req, res) {
|
||||
let result = characters.find(c => c.id == req.params.characterId);
|
||||
if (typeof result !== 'undefined') {
|
||||
return res.send(result);
|
||||
}
|
||||
return res.status(404).send({ message: "Character not found" });
|
||||
};
|
||||
|
||||
exports.delete_all = function (req, res) {
|
||||
characters = [];
|
||||
saveCharactersToFile();
|
||||
return res.send({ message: 'All characters deleted' });
|
||||
};
|
||||
|
||||
exports.reset = function (req, res) {
|
||||
characters = JSON.parse(JSON.stringify(initialCharacters));
|
||||
saveCharactersToFile();
|
||||
return res.send({ message: 'Characters reset', characters: characters });
|
||||
};
|
||||
|
||||
exports.delete_character = function (req, res) {
|
||||
let result = characters.find(c => c.id == req.params.characterId);
|
||||
if (typeof result === 'undefined') {
|
||||
return res.status(404).send({ message: "Character not found" });
|
||||
}
|
||||
characters = characters.filter(c => c.id != req.params.characterId);
|
||||
saveCharactersToFile();
|
||||
return res.send({ message: 'Character deleted' });
|
||||
};
|
||||
|
||||
exports.update_character = function (req, res) {
|
||||
let result = characters.find(c => c.id == req.params.characterId);
|
||||
if (typeof result === 'undefined') {
|
||||
return res.status(404).send({ message: "Character not found" });
|
||||
}
|
||||
|
||||
var newChar = Object.assign({}, req.body, { id: result.id });
|
||||
const validation = schema.validate(newChar);
|
||||
const { value, error } = validation;
|
||||
|
||||
if (error) {
|
||||
return res.status(422).json({
|
||||
message: 'Invalid character request',
|
||||
data: error
|
||||
});
|
||||
}
|
||||
|
||||
const formattedCharacter = Object.assign({ id: value.id, name: value.name }, value);
|
||||
|
||||
characters = characters.filter(c => c.id != req.params.characterId);
|
||||
characters.push(formattedCharacter);
|
||||
saveCharactersToFile();
|
||||
|
||||
return res.status(201).json({
|
||||
message: "Character updated",
|
||||
character: { id: formattedCharacter.id }
|
||||
});
|
||||
};
|
||||
|
||||
exports.new_character = function (req, res) {
|
||||
var len = 24;
|
||||
var pattern = '0123456789abcdef';
|
||||
|
||||
var newChar = Object.assign({}, req.body, { id: randomId(len, pattern) });
|
||||
|
||||
let existing = characters.find(c => c.name === newChar.name);
|
||||
if (typeof existing !== 'undefined') {
|
||||
return res.status(400).send({
|
||||
message: 'Character ' + newChar.name + ' already exists'
|
||||
});
|
||||
}
|
||||
|
||||
const validation = schema.validate(newChar);
|
||||
const { value, error } = validation;
|
||||
|
||||
if (error) {
|
||||
return res.status(422).json({
|
||||
message: 'Invalid request',
|
||||
data: error
|
||||
});
|
||||
}
|
||||
|
||||
const formattedCharacter = Object.assign({ id: value.id, name: value.name }, value);
|
||||
|
||||
characters.push(formattedCharacter);
|
||||
saveCharactersToFile();
|
||||
|
||||
return res.status(201).json({
|
||||
message: "Character created",
|
||||
character: { id: formattedCharacter.id }
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
var characters = require('../characters.json');
|
||||
const charactersController = require('../controllers/charactersController.js');
|
||||
var basicAuth = require('express-basic-auth');
|
||||
|
||||
router.use(basicAuth({
|
||||
@@ -8,17 +8,14 @@ router.use(basicAuth({
|
||||
unauthorizedResponse: {
|
||||
message: "Sorry Wizard, can't let you in."
|
||||
}
|
||||
}));
|
||||
|
||||
}))
|
||||
|
||||
router.get('/',
|
||||
function (req, res) {
|
||||
res.json(characters);
|
||||
});
|
||||
|
||||
router.get('/:characterId', function (req, res) {
|
||||
let result = characters.find(character => character.id == req.params.characterId);
|
||||
res.send(result);
|
||||
});
|
||||
router.get('/', charactersController.characters_list);
|
||||
router.post('/', charactersController.new_character);
|
||||
router.delete('/actions/deleteAll', charactersController.delete_all);
|
||||
router.get('/actions/reset', charactersController.reset);
|
||||
router.get('/:characterId', charactersController.specific_character);
|
||||
router.delete('/:characterId', charactersController.delete_character);
|
||||
router.put('/:characterId', charactersController.update_character);
|
||||
|
||||
module.exports = router;
|
||||
Reference in new issue
Block a user