# QA 10: API testing (1) — REST, JSON, status kodlar va Postman amaliyoti

> **Holat:** 2026-yil oktabr. Bu darsda REST API'ning tuzilishi, method'lar, status kodlar, JSON va autentifikatsiya asoslarini o'rganamiz, so'ng `curl` va Postman bilan haqiqiy API'ni testlaymiz. Postman bo'yicha ma'lumotlar Postman Docs'ning joriy (v12) versiyasiga asoslangan; darsdagi Postman collection'i Newman 6.2.3 bilan restful-booker'ga qarshi ishga tushirilib, 18 ta assertion'ning hammasi o'tgani tekshirilgan.

> **Bu darsdan keyin siz:** istalgan REST API hujjatini o'qib, uni `curl` va Postman bilan mustaqil testlay olasiz.

- method, status kod va header'larni "ko'rib turib" tushunasiz;
- JSON va so'rov qismlarini (path, query, header, body) to'g'ri tuza olasiz;
- API key, Basic, Bearer/JWT va cookie autentifikatsiyasini farqlaysiz;
- Postman'da collection, environment, variable va skriptlar bilan to'liq CRUD testini yozasiz;
- testlarni Newman bilan terminalda (va keyinchalik CI'da) ishga tushirasiz.

## Mundarija

1. [API nima va QA uchun nega muhim](#api-nima)
2. [REST printsiplari va endpoint dizayni](#rest)
3. [Method'lar, CRUD va idempotentlik](#methods)
4. [Status kodlar — QA uchun to'liq jadval](#status-codes)
5. [JSON asoslari](#json)
6. [So'rov qismlari: path, query, header, body](#request-parts)
7. [Autentifikatsiya asoslari](#auth)
8. [Swagger / OpenAPI hujjatini o'qish](#openapi)
9. [curl bilan birinchi API testlar](#curl)
10. [Postman asoslari: collection, environment, variable](#postman-basics)
11. [Postman skriptlari: Pre-request va Post-response](#postman-scripts)
12. [Collection Runner va data-driven testlar](#runner)
13. [Newman — Postman'ni terminalda ishga tushirish](#newman)
14. [restful-booker g'alatiliklari: bug'mi yoki xususiyat?](#quirks)
15. [Amaliy mashqlar](#mashqlar)
16. [Xulosa](#xulosa)
17. [Manbalar](#manbalar)

---

## 1. API nima va QA uchun nega muhim {#api-nima}

**API** (Application Programming Interface) — dasturlar bir-biri bilan gaplashadigan "shartnoma". Restoran menyusiga o'xshaydi: menyuda nima buyurish mumkinligi (endpoint'lar), qanday so'rash (method, parametrlar) va nima olib kelinishi (javob formati) yozilgan. Oshxona ichida nima bo'layotganini mijoz bilmaydi va bilishi shart ham emas.

DemoBozor'da web sayt, iOS va Android ilova — uchalasi ham **bitta API**'ga murojaat qiladi. Demak, API'dagi bitta bug uchta platformada birdaniga bug bo'lib chiqadi. Shuning uchun API testlash QA uchun eng foydali ko'nikmalardan biri:

| UI orqali testlash                                | API orqali testlash                                            |
| ------------------------------------------------- | -------------------------------------------------------------- |
| Sekin: sahifa yuklanishi, animatsiyalar           | Tez: bitta so'rov — millisekundlar                             |
| UI o'zgarsa test buziladi                         | API shartnomasi kamroq o'zgaradi                               |
| Faqat UI ruxsat bergan qiymatlarni kiritasiz      | Istalgan qiymatni yuborasiz (manfiy narx, 10 000 belgilik ism) |
| Bug'ning qaysi qatlamda ekanini aniqlash qiyinroq | Back-end'ni to'g'ridan-to'g'ri tekshirasiz                     |

API turlari: **REST** (eng keng tarqalgan, bu darsning mavzusi), **GraphQL**, **gRPC**, eski tizimlarda **SOAP**, real vaqt uchun **WebSocket**. GraphQL, WebSocket va gRPC'ning QA uchun xususiyatlari [QA 11](/ios-lessons/qa-api-advanced) da.

> **🎯 Intervyuda:** "Nega API testlash kerak, UI testlar yetmaydimi?" — API testlar tezroq, barqarorroq, back-end mantiqni UI cheklovlarisiz tekshiradi va bug'ni aniq qatlamga bog'laydi. Test piramidasida ular UI testlardan pastda, ya'ni ko'proq bo'lishi kerak.

---

## 2. REST printsiplari va endpoint dizayni {#rest}

**REST** (Representational State Transfer) — Roy Fielding 2000-yilda dissertatsiyasida ta'riflagan arxitektura uslubi. QA uchun eng muhim g'oyalari:

- **Resurs** — har bir "narsa" (mahsulot, buyurtma, foydalanuvchi) o'z manziliga (URI) ega: `/products/1042`.
- **Representation** — resurs odatda JSON ko'rinishida uzatiladi.
- **Bir xil interfeys** — resurs ustidagi amal HTTP method bilan ifodalanadi, URL'da fe'l bo'lmaydi.
- **Stateless** — har bir so'rov o'zi uchun kerakli hamma narsani (masalan, token) olib keladi; server oldingi so'rovni "eslamaydi".
- **Client-server, keshlanadigan, qatlamli tizim** — client server ichini bilmaydi, javoblar keshlanishi mumkin, oraliqda proxy/CDN bo'lishi mumkin.

### Yaxshi va yomon endpoint'lar

| Yomon                               | Yaxshi (REST uslubi)                 | Izoh                      |
| ----------------------------------- | ------------------------------------ | ------------------------- |
| `GET /getProducts`                  | `GET /products`                      | Fe'l method'da, URL'da ot |
| `POST /deleteOrder?id=5`            | `DELETE /orders/5`                   | Amal turi — method        |
| `GET /product/5` va `GET /products` | `GET /products/5` va `GET /products` | Bir xil ko'plik shakli    |
| `GET /orders/5/getItems`            | `GET /orders/5/items`                | Ichma-ich resurs          |
| `/v1products`                       | `/api/v1/products`                   | Versiya alohida segmentda |

QA nuqtai nazaridan endpoint nomlari "go'zallik" emas: aralash uslub — hujjat va SDK'larda xatolarga, dasturchilar orasida noto'g'ri tushunishga olib keladi. Yangi API dizaynini review qilishda bunday nomuvofiqliklarni ko'rsating.

---

## 3. Method'lar, CRUD va idempotentlik {#methods}

![API so'rovi va javobining anatomiyasi hamda method, CRUD va idempotentlik jadvali](/lessons/images/qa/qa_10_api_anatomy.svg)

| Method    | CRUD                                      | Safe | Idempotent      | Odatiy muvaffaqiyat kodi | Body        |
| --------- | ----------------------------------------- | ---- | --------------- | ------------------------ | ----------- |
| `GET`     | Read                                      | ✅   | ✅              | `200`                    | Yo'q        |
| `POST`    | Create                                    | ❌   | ❌              | `201` (+ `Location`)     | Bor         |
| `PUT`     | Update (to'liq almashtirish)              | ❌   | ✅              | `200` yoki `204`         | Bor         |
| `PATCH`   | Update (qisman)                           | ❌   | Kafolatlanmaydi | `200` yoki `204`         | Bor         |
| `DELETE`  | Delete                                    | ❌   | ✅              | `200` yoki `204`         | Odatda yo'q |
| `HEAD`    | Faqat header'lar                          | ✅   | ✅              | `200`                    | Yo'q        |
| `OPTIONS` | Ruxsat etilgan method'lar, CORS preflight | ✅   | ✅              | `200`/`204`              | Yo'q        |

- **Safe** — server holatini o'zgartirmaydi (faqat o'qiydi).
- **Idempotent** — bir xil so'rovni 1 marta yoki 10 marta yuborish server holatiga **bir xil** ta'sir qiladi. `DELETE /cart/items/7` ikki marta yuborilsa ham, natijada element baribir o'chgan bo'ladi.

QA uchun amaliy oqibat: tarmoq uzilib, ilova so'rovni avtomatik **qayta yuborsa**, idempotent bo'lmagan `POST /orders` **ikkita buyurtma** yaratishi mumkin. Real misol: restful-booker'ga bir xil `POST /booking` ni ikki marta yuborganimizda ikki xil `bookingid` (1611 va 1623) qaytdi — ya'ni ikkita booking yaratildi. Booking yoki to'lov uchun bu jiddiy risk. Ba'zi API'lar (ayniqsa to'lov tizimlari) buning oldini olish uchun mijozdan `Idempotency-Key` kabi header kutadi — loyihangizda shunday mexanizm bormi, so'rang va testlang.

### PUT va PATCH farqi

- `PUT` — resursni **to'liq** almashtiradi: nazariyada yuborilmagan ixtiyoriy maydon o'chishi yoki standart qiymatga qaytishi kerak.
- `PATCH` — faqat yuborilgan maydonlarni o'zgartiradi.

restful-booker'da tekshirdik: `PATCH` bilan faqat `{"firstname":"Bobur"}` yuborilganda boshqa maydonlar joyida qoldi — to'g'ri. Lekin `PUT` da `additionalneeds` maydoni yuborilmaganida ham u `"Breakfast"` bo'lib saqlanib qoldi — ya'ni bu API'da `PUT` aslida "birlashtirish" (merge) kabi ishlaydi. Bu hujjat va HTTP semantikasi bo'yicha savol: jamoangiz qaysi xatti-harakatni kutishini aniqlab, test'ni shunga yozing.

---

## 4. Status kodlar — QA uchun to'liq jadval {#status-codes}

| Kod           | Nomi                   | Qachon                                       | DemoBozor misoli                              | QA eslatmasi                                                                                  |
| ------------- | ---------------------- | -------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `200`         | OK                     | Muvaffaqiyatli o'qish/yangilash              | `GET /products/1042`                          | Body'ni ham tekshiring — `200` ichida xato bo'lishi mumkin                                    |
| `201`         | Created                | Yangi resurs yaratildi                       | `POST /orders`                                | `Location` header'i bormi                                                                     |
| `204`         | No Content             | Muvaffaqiyat, body yo'q                      | `DELETE /cart/items/7`                        | Body bo'sh bo'lishi kerak                                                                     |
| `301` / `302` | Moved / Found          | Yo'naltirish                                 | `http://` → `https://`                        | Yo'naltirish zanjiri cheksiz emasmi                                                           |
| `304`         | Not Modified           | Kesh hamon dolzarb                           | `If-None-Match` bilan GET                     | [QA 9](/ios-lessons/qa-web-http-devtools) dagi kesh tajribasi                                 |
| `400`         | Bad Request            | So'rov noto'g'ri tuzilgan                    | Buzilgan JSON                                 | Xato xabari tushunarlimi                                                                      |
| `401`         | Unauthorized           | Autentifikatsiya yo'q yoki yaroqsiz          | Token yo'q/muddati o'tgan                     | "Kimligingiz noma'lum"                                                                        |
| `403`         | Forbidden              | Kimligingiz ma'lum, lekin ruxsat yo'q        | Xaridor admin endpoint'iga                    | "Siz kimligingizni bilaman, lekin mumkin emas"                                                |
| `404`         | Not Found              | Resurs yo'q                                  | `GET /orders/999999`                          | Boshqa birovning resursi uchun ham `404` qaytarish mumkin (ma'lumot oshkor bo'lmasligi uchun) |
| `405`         | Method Not Allowed     | Bu resursda bu method yo'q                   | `DELETE /products`                            | Javobda `Allow` header'i bo'lishi kerak                                                       |
| `409`         | Conflict               | Holat ziddiyati                              | Ro'yxatdan o'tgan telefon bilan qayta ro'yxat | Xabar aniq bo'lsin                                                                            |
| `415`         | Unsupported Media Type | Body formati qo'llab-quvvatlanmaydi          | JSON o'rniga `text/plain`                     | `500` emas, aynan `415` kutiladi                                                              |
| `422`         | Unprocessable Content  | Format to'g'ri, qiymatlar qoidaga zid        | `quantity: 120` (ruxsat 1–99)                 | Qaysi maydon xato — javobda ko'rsatilsinmi                                                    |
| `429`         | Too Many Requests      | Limitdan oshdi                               | 3 noto'g'ri SMS kod                           | `Retry-After` header'i bormi                                                                  |
| `500`         | Internal Server Error  | Serverda kutilmagan xato                     | Har qanday ishlov berilmagan holat            | Deyarli har doim bug; stack trace oshkor bo'lmasin                                            |
| `502`         | Bad Gateway            | Proxy orqadagi xizmatdan yaroqsiz javob oldi | API konteyneri qulagan                        | Infra bilan birga ko'riladi                                                                   |
| `503`         | Service Unavailable    | Vaqtincha ishlamaydi                         | Texnik ishlar                                 | `Retry-After` bo'lishi mumkin                                                                 |
| `504`         | Gateway Timeout        | Proxy javobni kutib charchadi                | Sekin hisobot so'rovi                         | Timeout chegaralari                                                                           |

> **🧠 Esda tuting:** `401` = "siz kimsiz?", `403` = "kimligingizni bilaman, lekin ruxsat yo'q". Toolshop API'da buni o'zingiz ko'rasiz: tokensiz `GET /users/me` → `401`, oddiy xaridor tokeni bilan admin'ga tegishli `GET /users` → `403` ([QA 11](/ios-lessons/qa-api-advanced)).

> **🎯 Intervyuda:** "`400` va `422` farqi?" — `400`: so'rovni umuman tushunib bo'lmaydi (buzilgan JSON, noto'g'ri sintaksis). `422`: so'rov to'g'ri tuzilgan, lekin qiymatlar biznes qoidalariga zid (masalan, miqdor 1–99 oralig'idan tashqarida). Ko'p jamoalar `400` ni ikkalasi uchun ham ishlatadi — muhimi, API ichida izchil bo'lsin.

---

## 5. JSON asoslari {#json}

JSON — API'larning asosiy ma'lumot formati. Atigi 6 ta tip bor:

| Tip     | Misol                             |
| ------- | --------------------------------- |
| string  | `"Choynak 1.7 L"`                 |
| number  | `450000`, `4.5`                   |
| boolean | `true`, `false`                   |
| null    | `null`                            |
| object  | `{"id": 1042, "name": "Choynak"}` |
| array   | `[1, 2, 3]`                       |

DemoBozor buyurtmasi JSON'da (xayoliy):

```json
{
  "id": 58231,
  "status": "PAID",
  "paymentMethod": "UZCARD",
  "promoCode": null,
  "items": [
    {
      "productId": 1042,
      "name": "Elektr choynak",
      "quantity": 1,
      "unitPrice": 289000
    },
    {
      "productId": 2210,
      "name": "Paxta ko'ylak",
      "quantity": 2,
      "unitPrice": 95000
    }
  ],
  "subtotal": 479000,
  "deliveryFee": 0,
  "total": 479000,
  "createdAt": "2026-10-09T14:05:00+05:00"
}
```

QA nimaga e'tibor beradi:

- **Tip**: `"quantity": "2"` (string) va `"quantity": 2` (number) — boshqa-boshqa narsa. Front-end `"2" + 1` qilsa `"21"` chiqadi.
- **`null`, yo'q maydon va bo'sh qator** — uch xil holat: `"promoCode": null`, `promoCode` umuman yo'q, `"promoCode": ""`. API hujjati qaysi biri qachon bo'lishini aytishi kerak.
- **Sana formati** — ISO 8601 va vaqt zonasi (`+05:00`, Toshkent). Zona yo'q sanalar — klassik bug manbai.
- **Pul** — so'mni butun son sifatida saqlash (`289000`) xavfsizroq; kasr sonlar yaxlitlash xatolariga olib kelishi mumkin.
- **Sintaksis xatolari**: oxirgi elementdan keyin vergul, bitta qo'shtirnoq (`'name'`), izoh (`// ...`) — JSON'da bularning hammasi taqiqlangan.

---

## 6. So'rov qismlari: path, query, header, body {#request-parts}

```text
PATCH https://api.demobozor.example/api/v1/cart/items/7?notify=false
      └──────────── host ─────┘└──── path ────────┘└─ query ──┘
Headers:  Authorization: Bearer <token>
          Content-Type: application/json
Body:     {"quantity": 3}
```

| Qism           | Nima uchun                          | Misol                                              |
| -------------- | ----------------------------------- | -------------------------------------------------- |
| Path parametr  | Aniq resursni ko'rsatish            | `/booking/{id}` → `/booking/252`                   |
| Query parametr | Filtr, saralash, sahifalash         | `?firstname=Aziz&lastname=Karimov`                 |
| Header         | Metama'lumot: format, auth, til     | `Content-Type`, `Authorization`, `Accept-Language` |
| Body           | Yaratiladigan/o'zgaradigan ma'lumot | JSON, form-data, fayl                              |

Body turlari: `application/json` (eng ko'p), `application/x-www-form-urlencoded` (oddiy web formalar), `multipart/form-data` (fayl yuklash). **`Content-Type` header'i body turiga mos bo'lishi shart** — aks holda server body'ni o'qiy olmaydi. Real tajriba: restful-booker'ga to'g'ri JSON'ni `Content-Type: text/plain` bilan yuborsangiz, `500 Internal Server Error` qaytadi ([14-bo'lim](#quirks)).

`Accept` header'i esa client qaysi formatda javob kutishini aytadi: restful-booker'da `Accept: application/xml` bilan `GET /booking/{id}` XML qaytaradi.

---

## 7. Autentifikatsiya asoslari {#auth}

**Autentifikatsiya** — "siz kimsiz?" savoli; **avtorizatsiya** — "sizga nima mumkin?" savoli. API'larda eng ko'p uchraydigan usullar:

| Usul         | Qanday yuboriladi                          | Misol                         | QA eslatmasi                                                   |
| ------------ | ------------------------------------------ | ----------------------------- | -------------------------------------------------------------- |
| API key      | Header yoki query                          | `X-API-Key: abc123`           | Kalit URL'da bo'lsa, loglarda qolib ketadi                     |
| Basic        | `Authorization: Basic base64(login:parol)` | restful-booker `PATCH`        | Base64 — **shifrlash emas**                                    |
| Bearer token | `Authorization: Bearer <token>`            | Toolshop API                  | Token muddati, bekor qilinishi                                 |
| Cookie       | `Cookie: token=...`                        | restful-booker `PUT`/`DELETE` | Cookie atributlari ([QA 9](/ios-lessons/qa-web-http-devtools)) |
| OAuth 2.0    | Token avtorizatsiya serveri orqali olinadi | "Google bilan kirish"         | Oqimlar: authorization code (+PKCE), client credentials        |

### Base64 — shifr emas

```bash
echo 'YWRtaW46cGFzc3dvcmQxMjM=' | base64 -d
# admin:password123
```

restful-booker hujjatidagi Basic header'i bir buyruq bilan ochildi. Shuning uchun Basic faqat HTTPS ustida ishlatilishi kerak.

### JWT tuzilishi

Ko'p Bearer token'lar **JWT** (JSON Web Token): nuqta bilan ajratilgan uch qism — `header.payload.signature`. Header va payload — base64url'da kodlangan oddiy JSON, ya'ni **shifrlanmagan**; signature esa token o'zgartirilmaganini tasdiqlaydi.

Toolshop API'dan olingan haqiqiy token'ni **o'z kompyuteringizda** ochamiz:

```bash
TOKEN=$(curl -s -X POST https://api.practicesoftwaretesting.com/users/login \
  -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -d '{"email":"customer@practicesoftwaretesting.com","password":"welcome01"}' \
  | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).access_token))')

node -e 'const p = process.argv[1].split(".")[1]; console.log(JSON.parse(Buffer.from(p, "base64url").toString()))' "$TOKEN"
# {
#   iss: 'https://api.practicesoftwaretesting.com/users/login',
#   iat: 1791566303,
#   exp: 1791566603,
#   ...
#   role: 'user'
# }
```

`exp - iat = 300` — token atigi **5 daqiqa** yashaydi. Bundan darhol test g'oyalari chiqadi: 5 daqiqadan keyin so'rov nima qaytaradi (`401` kutiladi)? Ilova token'ni avtomatik yangilaydimi (refresh)? Payload'dagi `role` qiymatini o'zgartirib yuborsak, server signature'ni tekshirib rad etadimi ([QA 11](/ios-lessons/qa-api-advanced))?

> **⚠️ Diqqat:** Real (production yoki ishxona) token'ini jwt.io kabi onlayn dekoderlarga **joylamang** — u amal qilish muddati davomida sizning nomingizdan kirish kaliti. Yuqoridagidek lokal buyruq bilan oching.

---

## 8. Swagger / OpenAPI hujjatini o'qish {#openapi}

**OpenAPI** — REST API'ni mashina o'qiy oladigan formatda (YAML/JSON) tasvirlash standarti. **Swagger UI** shu faylni interaktiv sahifaga aylantiradi. Toolshop API hujjati, masalan, OpenAPI 3.2.0 formatida yozilgan (2026-yil oktabr holati).

Mashq uchun klassik **Swagger Petstore**'ni oching: `https://petstore.swagger.io` (Swagger 2.0) yoki `https://petstore3.swagger.io` (OpenAPI 3). Hujjatda nimani qidiramiz:

| Hujjat qismi       | Petstore'dagi misol                                   | Undan qanday test chiqadi          |
| ------------------ | ----------------------------------------------------- | ---------------------------------- |
| Tag'lar (guruhlar) | `pet`, `store`, `user`                                | Qamrov rejasini guruhlarga bo'lish |
| Endpoint           | `GET /pet/{petId}`                                    | Har endpoint uchun test to'plami   |
| Parametr           | `petId` — path, `integer`, `int64`, majburiy          | `abc`, `-1`, `0`, juda katta son   |
| Enum               | `findByStatus`: `available`, `pending`, `sold`        | Ro'yxatdan tashqari qiymat         |
| Majburiy maydonlar | `Pet` modelida `name`, `photoUrls`                    | Har birisiz yuborish               |
| Javoblar           | `200`, `400 Invalid ID supplied`, `404 Pet not found` | Har bir kod uchun alohida test     |
| Xavfsizlik         | `api_key`, `petstore_auth` (OAuth)                    | Kalitsiz/noto'g'ri kalit bilan     |
| "Try it out"       | So'rovni brauzerdan yuborish                          | Tez qo'lda tekshiruv               |

**Real topilma** (2026-yil oktabr): hujjatga ko'ra noto'g'ri ID uchun `400 Invalid ID supplied` kutiladi. Amalda:

```bash
curl -s https://petstore.swagger.io/v2/pet/abc -H 'Accept: application/json'
# {"code":404,"type":"unknown","message":"java.lang.NumberFormatException: For input string: \"abc\""}
```

Bitta javobda ikkita muammo: (1) hujjat `400` desa ham server `404` qaytaryapti — **hujjat va amaliyot mos emas**; (2) javobda ichki Java exception nomi oshkor bo'lyapti — kichik **xavfsizlik topilmasi** (texnologiya haqida ma'lumot sizib chiqadi). Mana shunday "hujjat vs haqiqat" solishtiruvi — API testlashning asosi.

---

## 9. curl bilan birinchi API testlar {#curl}

Restful-booker — API testlashni mashq qilish uchun ochiq API (hujjati: `https://restful-booker.herokuapp.com/apidoc/index.html`). Ma'lumotlar vaqti-vaqti bilan tozalanadi, shuning uchun testlar o'z booking'ini o'zi yaratishi kerak. Quyidagi barcha javoblar haqiqiy (qisqartirilgan).

### 1. Server tirikmi?

```bash
curl -i https://restful-booker.herokuapp.com/ping
# HTTP/2 201
# Created
```

### 2. Token olish

```bash
curl -s -X POST https://restful-booker.herokuapp.com/auth \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"password123"}'
# {"token":"30d598abd9f88b0"}
```

Bash'da token'ni o'zgaruvchiga saqlab olamiz:

```bash
BASE=https://restful-booker.herokuapp.com
TOKEN=$(curl -s -X POST $BASE/auth -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"password123"}' \
  | python3 -c 'import sys, json; print(json.load(sys.stdin)["token"])')
echo $TOKEN
```

### 3. Booking yaratish (Create)

```bash
curl -s -X POST $BASE/booking \
  -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -d '{"firstname":"Aziz","lastname":"Karimov","totalprice":450,"depositpaid":true,
       "bookingdates":{"checkin":"2026-11-01","checkout":"2026-11-05"},
       "additionalneeds":"Breakfast"}'
# HTTP status: 200
# {"bookingid":252,"booking":{"firstname":"Aziz","lastname":"Karimov","totalprice":450,...}}
```

### 4. O'qish (Read), yangilash (Update), o'chirish (Delete)

```bash
ID=252   # o'zingizdagi bookingid

curl -s $BASE/booking/$ID -H 'Accept: application/json'
# 200 {"firstname":"Aziz","lastname":"Karimov","totalprice":450,...}

# Tokensiz PUT — rad etilishi kerak
curl -s -o /dev/null -w "%{http_code}\n" -X PUT $BASE/booking/$ID \
  -H 'Content-Type: application/json' -d '{"firstname":"X"}'
# 403

# Cookie token bilan PUT (to'liq body)
curl -s -X PUT $BASE/booking/$ID \
  -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -H "Cookie: token=$TOKEN" \
  -d '{"firstname":"Aziz","lastname":"Karimov","totalprice":500,"depositpaid":true,
       "bookingdates":{"checkin":"2026-11-01","checkout":"2026-11-06"},
       "additionalneeds":"Breakfast"}'
# 200 {"firstname":"Aziz",...,"totalprice":500,...}

# Basic auth bilan PATCH (faqat bitta maydon)
curl -s -X PATCH $BASE/booking/$ID \
  -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -H 'Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM=' \
  -d '{"totalprice":520}'
# 200 {...,"totalprice":520,...}

curl -s -i -X DELETE $BASE/booking/$ID -H "Cookie: token=$TOKEN"
# HTTP/2 201
# Created

curl -s -o /dev/null -w "%{http_code}\n" $BASE/booking/$ID
# 404
```

> **💡 Maslahat:** `curl` buyruqlarini README yoki bug report'ga qo'shing — dasturchi ularni nusxalab, sizning holatingizni bir soniyada takrorlaydi. DevTools'dagi **Copy as cURL** xuddi shu formatni beradi ([QA 9](/ios-lessons/qa-web-http-devtools)).

---

## 10. Postman asoslari: collection, environment, variable {#postman-basics}

**Postman** — API'larni qo'lda va avtomatik testlash uchun eng mashhur grafik vosita. Desktop ilovani yuklab o'rnating (web versiyasi ham bor).

> **🇺🇿 Mahalliy kontekst:** 2026-yil martidan Postman'ning bepul (Free) rejasi **faqat bitta foydalanuvchi** uchun: jamoaviy workspace'lar pullik rejalarga (Solo, Team, Enterprise) o'tdi. O'rganish uchun Free yetarli. Jamoada bepul ishlash kerak bo'lsa — **Bruno** (ochiq kodli, collection'larni git'da saqlaydi), **Hoppscotch** yoki **Insomnia**. Tushunchalar (collection, environment, variable, skript) bir xil, shu darsdagi bilimlar to'g'ridan-to'g'ri o'tadi.

### Asosiy tushunchalar

| Tushuncha   | Ma'nosi                                                                                |
| ----------- | -------------------------------------------------------------------------------------- |
| Workspace   | Loyiha uchun ish maydoni                                                               |
| Collection  | So'rovlar to'plami (folder'larga bo'linadi) — test suite'ingiz                         |
| Request     | Bitta so'rov: method, URL, header'lar, body, skriptlar                                 |
| Environment | O'zgaruvchilar to'plami: `local`, `staging`, `prod` — bitta tugma bilan almashtiriladi |
| Variable    | `{{baseUrl}}` kabi qo'yiladigan qiymat                                                 |
| History     | Yuborilgan so'rovlar tarixi                                                            |

### O'zgaruvchilar va ularning sohasi (scope)

Postman'da 5 ta scope bor (kengdan torga): **global → collection → environment → data → local**. Bir xil nomli o'zgaruvchi bir nechta scope'da bo'lsa, **eng tor** scope'dagi qiymat ishlatiladi.

| Scope       | Qachon ishlatiladi                                         | Skriptda                         |
| ----------- | ---------------------------------------------------------- | -------------------------------- |
| Global      | Barcha collection'lar uchun umumiy (kamdan-kam kerak)      | `pm.globals.set/get`             |
| Collection  | Collection ichidagi umumiy qiymatlar: `token`, `bookingId` | `pm.collectionVariables.set/get` |
| Environment | Muhitga bog'liq: `baseUrl`, login                          | `pm.environment.set/get`         |
| Data        | Runner'dagi CSV/JSON fayldan har iteratsiya uchun          | `pm.iterationData.get`           |
| Local       | Faqat joriy so'rov/run davomida                            | `pm.variables.set`               |

`pm.variables.get("x")` esa barcha scope'lardan eng torining qiymatini qaytaradi.

Postman'ning yangi versiyalarida o'zgaruvchining ikki qiymati **shared value** (bulutga sinxronlanadi, jamoa ko'radi) va **local value** (faqat sizning kompyuteringizda) deb ataladi; eski hujjatlarda ular _initial_ va _current value_ edi. Parol va token'lar uchun **Postman Vault**'dan foydalaning.

> **⚠️ Diqqat:** `prod` environment'ida yaratish/o'chirish testlarini ishga tushirmang. Ko'p jamoalar prod environment'ini umuman collection'ga qo'shmaydi yoki uni faqat o'qish (GET) testlari bilan cheklaydi.

### Environment fayli (eksport ko'rinishi)

```json
{
  "name": "restful-booker-public",
  "values": [
    {
      "key": "baseUrl",
      "value": "https://restful-booker.herokuapp.com",
      "enabled": true
    },
    { "key": "username", "value": "admin", "enabled": true },
    { "key": "password", "value": "password123", "enabled": true }
  ]
}
```

So'rov URL'ida endi `{{baseUrl}}/booking` yozamiz — environment'ni `staging` ga almashtirsangiz, barcha so'rovlar o'sha serverga ketadi.

---

## 11. Postman skriptlari: Pre-request va Post-response {#postman-scripts}

Har bir request, folder va collection'ning **Scripts** tab'ida ikkita bo'lim bor:

- **Pre-request** — so'rov yuborilishidan **oldin** ishlaydi: o'zgaruvchi tayyorlash, sana generatsiya qilish, token bo'lmasa so'rovni o'tkazib yuborish.
- **Post-response** — javob kelgandan **keyin**: assertion'lar (testlar) va keyingi so'rov uchun qiymatlarni saqlash. Eski versiyalarda bu alohida **Tests** tab'i edi (v11 dan boshlab Scripts ichida).

Ishlash tartibi: avval collection, keyin folder, so'ng request darajasidagi skriptlar. Natijalar javob panelidagi **Test Results** tab'ida ko'rinadi. Assertion'lar Chai kutubxonasining BDD sintaksisida yoziladi (`pm.expect(x).to.eql(y)`).

### `pm` API — eng kerakli qismlar

| Kod                                       | Nima qiladi                                                             |
| ----------------------------------------- | ----------------------------------------------------------------------- |
| `pm.test("nom", () => { ... })`           | Bitta test (assertion'lar guruhi)                                       |
| `pm.expect(qiymat).to.eql(...)`           | Chai assertion                                                          |
| `pm.response.to.have.status(200)`         | Status kod                                                              |
| `pm.response.code`                        | Status kod raqami                                                       |
| `pm.response.json()`                      | Body'ni JSON sifatida olish                                             |
| `pm.response.headers.get("Content-Type")` | Header qiymati                                                          |
| `pm.response.responseTime`                | Javob vaqti (ms)                                                        |
| `pm.response.to.have.jsonSchema(schema)`  | JSON Schema bo'yicha tekshirish ([QA 11](/ios-lessons/qa-api-advanced)) |
| `pm.collectionVariables.set("k", v)`      | Collection o'zgaruvchisini saqlash                                      |
| `pm.iterationData.get("k")`               | Data fayldagi joriy qator qiymati                                       |
| `pm.execution.skipRequest()`              | Pre-request'da: so'rovni yubormaslik                                    |
| `pm.execution.setNextRequest("nom")`      | Collection run'da keyingi so'rovni tanlash                              |
| `pm.sendRequest(...)`                     | Skript ichidan qo'shimcha so'rov yuborish                               |

### To'liq CRUD collection (tekshirilgan)

Collection 9 ta so'rovdan iborat: `01 Ping` → `02 Auth` → `03 Booking yaratish` → `04 O'qish` → `05 PUT tokensiz (negative)` → `06 PUT` → `07 PATCH (Basic Auth)` → `08 DELETE` → `09 O'chirilganini tekshirish`. URL'larda `{{baseUrl}}`, body'larda `{{checkin}}`, `{{checkout}}`, header'da `Cookie: token={{token}}` ishlatiladi. Quyida har bir so'rovning skriptlari — aynan Newman'da o'tgan versiyalari.

**02 Auth — Post-response:**

```javascript
pm.test("Status 200", () => {
  pm.response.to.have.status(200);
});

const body = pm.response.json();

pm.test("Token qaytdi (faqat statusga ishonmaymiz!)", () => {
  pm.expect(body.token, "token maydoni").to.be.a("string").and.not.be.empty;
});

pm.collectionVariables.set("token", body.token);
```

Nega "faqat statusga ishonmaymiz"? Chunki restful-booker **noto'g'ri parol** uchun ham `200` qaytaradi — faqat body'da `{"reason":"Bad credentials"}` bo'ladi. Faqat statusni tekshiradigan test bu holatda yashil bo'lib qolardi.

**03 Booking yaratish — Pre-request:**

```javascript
// Bugundan 30 va 35 kun keyingi sanalar (YYYY-MM-DD)
const addDays = (days) => {
  const d = new Date();
  d.setDate(d.getDate() + days);
  return d.toISOString().slice(0, 10);
};

pm.collectionVariables.set("checkin", addDays(30));
pm.collectionVariables.set("checkout", addDays(35));
```

Body (raw → JSON):

```json
{
  "firstname": "Aziz",
  "lastname": "Karimov",
  "totalprice": 450,
  "depositpaid": true,
  "bookingdates": { "checkin": "{{checkin}}", "checkout": "{{checkout}}" },
  "additionalneeds": "Breakfast"
}
```

**03 Booking yaratish — Post-response:**

```javascript
pm.test("Status 200 (restful-booker 201 emas, 200 qaytaradi)", () => {
  pm.response.to.have.status(200);
});

const body = pm.response.json();

pm.test("bookingid — raqam", () => {
  pm.expect(body.bookingid).to.be.a("number");
});

pm.test("Yuborilgan ma'lumot o'zgarmay qaytdi", () => {
  pm.expect(body.booking.firstname).to.eql("Aziz");
  pm.expect(body.booking.totalprice).to.eql(450);
  pm.expect(body.booking.bookingdates.checkin).to.eql(
    pm.collectionVariables.get("checkin"),
  );
});

const schema = {
  type: "object",
  required: ["bookingid", "booking"],
  properties: {
    bookingid: { type: "integer" },
    booking: {
      type: "object",
      required: [
        "firstname",
        "lastname",
        "totalprice",
        "depositpaid",
        "bookingdates",
      ],
      properties: {
        firstname: { type: "string" },
        lastname: { type: "string" },
        totalprice: { type: "integer" },
        depositpaid: { type: "boolean" },
        bookingdates: {
          type: "object",
          required: ["checkin", "checkout"],
          properties: {
            checkin: { type: "string" },
            checkout: { type: "string" },
          },
        },
      },
    },
  },
};

pm.test("Javob JSON Schema'ga mos", () => {
  pm.response.to.have.jsonSchema(schema);
});

pm.collectionVariables.set("bookingId", body.bookingid);
```

**04 O'qish — Post-response** (URL: `{{baseUrl}}/booking/{{bookingId}}`):

```javascript
pm.test("Status 200", () => {
  pm.response.to.have.status(200);
});

pm.test("Content-Type — JSON", () => {
  pm.expect(pm.response.headers.get("Content-Type")).to.include(
    "application/json",
  );
});

pm.test("Javob vaqti 3000 ms dan kam", () => {
  pm.expect(pm.response.responseTime).to.be.below(3000);
});

const booking = pm.response.json();

pm.test("Ma'lumot to'g'ri saqlangan", () => {
  pm.expect(booking.lastname).to.eql("Karimov");
  pm.expect(booking.depositpaid).to.be.true;
});
```

**05 PUT tokensiz (negative)**, **06 PUT** va **07 PATCH**:

```javascript
// 05 — Cookie'siz PUT
pm.test("Tokensiz PUT rad etiladi (403)", () => {
  pm.response.to.have.status(403);
});
```

```javascript
// 06 — header: Cookie: token={{token}}, body'da totalprice: 500
pm.test("Status 200", () => {
  pm.response.to.have.status(200);
});

pm.test("Narx 500 ga yangilandi", () => {
  pm.expect(pm.response.json().totalprice).to.eql(500);
});
```

```javascript
// 07 — Authorization tab: Basic Auth ({{username}} / {{password}}), body: {"totalprice": 520}
pm.test("Status 200", () => {
  pm.response.to.have.status(200);
});

pm.test("Faqat narx o'zgardi, ism joyida", () => {
  const b = pm.response.json();
  pm.expect(b.totalprice).to.eql(520);
  pm.expect(b.firstname).to.eql("Aziz");
});
```

**08 DELETE** va **09 O'chirilganini tekshirish:**

```javascript
// restful-booker o'chirishda 201 Created qaytaradi — bu "quirk".
// Odatiy REST'da 200 yoki 204 kutiladi, shuning uchun uchalasini qabul qilamiz.
pm.test("O'chirish muvaffaqiyatli", () => {
  pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]);
});
```

```javascript
pm.test("O'chirilgan booking topilmaydi (404)", () => {
  pm.response.to.have.status(404);
});
```

> **🧠 Esda tuting:** Yaxshi API test'i bitta assertion bilan cheklanmaydi: status + header + body qiymatlari + sxema + (kerak bo'lsa) vaqt. Va eng muhimi — **yon ta'sir**: o'chirilgan narsa haqiqatan yo'qolganini alohida GET bilan tekshirish.

### Ko'p uchraydigan xatolar

- Faqat statusni tekshirish (yuqoridagi `200 + Bad credentials` misoli).
- ID'ni qo'lda yozib qo'yish (`/booking/252`) — ertaga u booking o'chib ketadi. ID har doim oldingi so'rovdan olinsin.
- Testlarni bir-biriga yashirin bog'lash: 4-so'rov faqat 3-sidan keyin ishlaydi, lekin bu hech qayerda yozilmagan.
- Token va parollarni collection'ning shared qiymatlariga yozib, eksport qilib yuborish.
- Umumiy (shared) staging'da juda qattiq `responseTime` chegarasi — test "beqaror" bo'lib qoladi. Performance'ni alohida o'lchang ([QA 23](/ios-lessons/qa-performance-k6)).

---

## 12. Collection Runner va data-driven testlar {#runner}

**Collection Runner** butun collection'ni ketma-ket ishga tushiradi: iteratsiyalar soni, so'rovlar orasidagi kechikish va **data fayl** (CSV yoki JSON) tanlash mumkin. Natijada qaysi test o'tdi, qaysi yiqildi — jadval ko'rinishida chiqadi.

**Data-driven** test — bitta so'rovni turli ma'lumotlar bilan ko'p marta ishga tushirish. `bookings.csv`:

```text
firstname,lastname,totalprice
Aziz,Karimov,450
Malika,Yusupova,1200
Sardor,Tursunov,0
```

So'rov body'si CSV ustunlarini `{{...}}` bilan oladi (`totalprice` qo'shtirnoqsiz — son bo'lib ketishi uchun):

```text
{
  "firstname": "{{firstname}}",
  "lastname": "{{lastname}}",
  "totalprice": {{totalprice}},
  "depositpaid": true,
  "bookingdates": { "checkin": "2026-12-01", "checkout": "2026-12-03" }
}
```

Post-response:

```javascript
const sent = {
  firstname: pm.iterationData.get("firstname"),
  totalprice: Number(pm.iterationData.get("totalprice")),
};
const body = pm.response.json();

pm.test(`${sent.firstname}: status 200`, () => {
  pm.response.to.have.status(200);
});

pm.test(`${sent.firstname}: narx o'zgarmay saqlandi`, () => {
  pm.expect(body.booking.totalprice).to.eql(sent.totalprice);
});
```

Runner'da data fayl sifatida `bookings.csv` ni tanlasangiz — 3 ta iteratsiya, 6 ta assertion. Haqiqiy loyihada CSV'ga chegaraviy qiymatlarni qo'yasiz (EP/BVA — [QA 5](/ios-lessons/qa-test-design-1)): `0`, manfiy, juda katta son, bo'sh ism, 50 belgilik ism va hokazo.

**Oqimni boshqarish** (faqat collection run'da ishlaydi, oddiy Send'da e'tiborga olinmaydi):

```javascript
// Pre-request: token bo'lmasa, so'rovni umuman yubormaymiz
if (!pm.environment.get("token")) {
  pm.execution.skipRequest();
}
```

```javascript
// Post-response: keyingi qadamni tanlash
pm.execution.setNextRequest("09 O'chirilganini tekshirish");
```

---

## 13. Newman — Postman'ni terminalda ishga tushirish {#newman}

**Newman** — Postman collection'larini buyruq qatoridan ishga tushiradigan rasmiy vosita. U testlaringizni CI'ga (GitHub Actions va boshqalar) olib kirishning eng oddiy yo'li ([QA 21](/ios-lessons/qa-ci-cd)).

Postman'da collection va environment'ni eksport qiling (`...` → Export, Collection v2.1 formati), so'ng:

```bash
# Node.js loyihasida bir marta o'rnatish
npm init -y
npm i -D newman

# Collection'ni environment bilan ishga tushirish
npx newman run restful-booker.postman_collection.json \
  -e restful-booker.postman_environment.json
```

Haqiqiy natija (qisqartirilgan):

```text
→ 02 Auth — token olish
  POST https://restful-booker.herokuapp.com/auth [200 OK, 774B, 413ms]
  ✓  Status 200
  ✓  Token qaytdi (faqat statusga ishonmaymiz!)
...
→ 08 DELETE
  DELETE https://restful-booker.herokuapp.com/booking/2930 [201 Created, 747B, 407ms]
  ✓  O'chirish muvaffaqiyatli

┌─────────────────────────┬───────────────────┬───────────────────┐
│                         │          executed │            failed │
├─────────────────────────┼───────────────────┼───────────────────┤
│                requests │                 9 │                 0 │
│              assertions │                18 │                 0 │
└─────────────────────────┴───────────────────┴───────────────────┘
```

Foydali parametrlar:

| Parametr                                                 | Vazifasi                                          |
| -------------------------------------------------------- | ------------------------------------------------- |
| `-e env.json`                                            | Environment fayli                                 |
| `-d bookings.csv`                                        | Data fayl (data-driven)                           |
| `-n 3`                                                   | Iteratsiyalar soni                                |
| `-r cli,junit --reporter-junit-export results/junit.xml` | Hisobot formatlari: konsol + JUnit XML (CI uchun) |
| `--bail`                                                 | Birinchi xatoda to'xtash                          |
| `--delay-request 200`                                    | So'rovlar orasida 200 ms kutish                   |
| `--timeout-request 10000`                                | Bitta so'rov uchun timeout (ms)                   |
| `--env-var "baseUrl=https://..."`                        | Environment o'zgaruvchisini buyruqdan berish      |

Newman hamma test o'tsa **0**, birortasi yiqilsa **1** exit code bilan tugaydi — CI pipeline shu bo'yicha "qizil" yoki "yashil" bo'ladi.

> **🎯 Intervyuda:** "Postman testlarini qanday avtomatlashtirasiz?" — Collection'ni eksport qilib, Newman bilan CI'da ishga tushiraman; environment'ni CI secret'laridan beraman; JUnit hisobotini CI'ga yuklayman. Murakkab loyihalarda esa API testlarni kod sifatida yozaman (Playwright `request` — [QA 20](/ios-lessons/qa-api-automation)).

---

## 14. restful-booker g'alatiliklari: bug'mi yoki xususiyat? {#quirks}

Restful-booker — "API testlashni mashq qilish uchun bepul API" (README'dagi ta'rif). Undagi g'alatiliklar ataylab qilinganmi yoki yo'qmi — README'da aytilmagan. Muhimi boshqa: QA ularni topa olishi va **to'g'ri tasniflashi** kerak. 2026-yil oktabrda kuzatilgan holatlar:

| #   | Kuzatuv                                                                     | Hujjatda                 | HTTP odati                     | QA bahosi                                                      |
| --- | --------------------------------------------------------------------------- | ------------------------ | ------------------------------ | -------------------------------------------------------------- |
| 1   | `GET /ping` → `201 Created`                                                 | `201`                    | `200` (hech narsa yaratilmadi) | Hujjatga mos, odatga zid — past ustuvorlik                     |
| 2   | `POST /booking` → `200`                                                     | `200`                    | `201` + `Location`             | Odatga zid; client'lar `201` ni kutsa muammo                   |
| 3   | Noto'g'ri parol bilan `POST /auth` → `200` + `{"reason":"Bad credentials"}` | Xato holati yozilmagan   | `401`                          | Bug: status bo'yicha ishlaydigan client'lar xatoni "ko'rmaydi" |
| 4   | `DELETE /booking/{id}` → `201 Created`                                      | `201`                    | `200`/`204`                    | Hujjatga mos, semantikaga zid                                  |
| 5   | Mavjud bo'lmagan ID'ga `PUT`/`PATCH`/`DELETE` → `405`, `Allow` header'isiz  | —                        | `404`                          | Bug: noto'g'ri kod va `405` uchun majburiy header yo'q         |
| 6   | Tokensiz `PUT` → `403`                                                      | —                        | `401`                          | Munozarali: "siz kimsiz" holati uchun `401` to'g'riroq         |
| 7   | `Accept: application/xml` → XML body, lekin `Content-Type: text/html`       | XML qo'llab-quvvatlanadi | `application/xml`              | Bug: client javobni noto'g'ri talqin qiladi                    |
| 8   | `Content-Type: text/plain` bilan yaroqli JSON → `500`                       | —                        | `415`                          | Bug: server xatoni ushlamayapti                                |
| 9   | Javob header'ida `x-powered-by: Express`                                    | —                        | Yashirilgani yaxshi            | Kichik xavfsizlik topilmasi (texnologiya oshkor)               |

> **💡 Maslahat:** Bunday holatni bug report'ga yozayotganda uch narsani solishtiring: **hujjat** nima deydi, **standart/odat** (RFC 9110) nima deydi va **amalda** nima bo'lyapti. Agar hujjat ham "201" desa — bu kod xatosi emas, **API dizayni** masalasi; uni product owner yoki arxitektor bilan muhokama qilish uchun ko'taring. Batafsil negative testlar va topilmalar: [QA 11](/ios-lessons/qa-api-advanced).

---

## 15. Amaliy mashqlar {#mashqlar}

### Mashq 1: Status kodni toping

Har bir holat uchun eng to'g'ri status kodni yozing:

1. DemoBozor'da yangi buyurtma muvaffaqiyatli yaratildi.
2. Savatchadagi element o'chirildi, javobda body yo'q.
3. Token muddati o'tgan.
4. Xaridor `GET /api/v1/admin/users` ga murojaat qildi.
5. `quantity: 120` yuborildi (ruxsat 1–99), JSON to'g'ri.
6. SMS kodni 4-marta noto'g'ri kiritish urinishi (3 tadan keyin blok).
7. JSON body'da yopilmagan qavs.
8. To'lov xizmati javob bermadi, gateway kutib, vaqt tugadi.

<details>
<summary>💡 Javobni ko'rish</summary>

1. `201 Created` (+ `Location: /api/v1/orders/{id}`)
2. `204 No Content`
3. `401 Unauthorized`
4. `403 Forbidden`
5. `422 Unprocessable Content` (ba'zi jamoalar `400` ishlatadi — API ichida izchil bo'lsa, qabul qilinadi)
6. `429 Too Many Requests` (+ `Retry-After`, masalan 900 soniya = 15 daqiqa) — yoki jamoa kelishgan maxsus kod; muhimi, hujjatda yozilgan bo'lsin
7. `400 Bad Request`
8. `504 Gateway Timeout`

</details>

### Mashq 2: Idempotentlik

Qaysi so'rovlarni tarmoq uzilganda avtomatik **xavfsiz qayta yuborish** mumkin? Nega?

`GET /products?query=choynak`, `POST /orders`, `PUT /profile`, `DELETE /cart/items/7`, `PATCH /cart/items/7 {"quantity": "+1"}`

<details>
<summary>💡 Javobni ko'rish</summary>

- `GET` — ha (safe va idempotent).
- `PUT /profile` — ha (bir xil to'liq holatni qayta yozadi).
- `DELETE /cart/items/7` — ha (ikkinchi marta ham natija — element yo'q; javob kodi `404` bo'lishi mumkin, lekin holat bir xil).
- `POST /orders` — **yo'q**: ikkita buyurtma yaratilishi mumkin (restful-booker'da ikki marta POST → ikki xil `bookingid`). Himoya: `Idempotency-Key` yoki server tomonda dublikat tekshiruvi.
- `PATCH ... "+1"` — **yo'q**: har yuborishda miqdor oshadi. `{"quantity": 3}` kabi absolyut qiymat yuborilsa idempotent bo'lardi.

</details>

### Mashq 3: GET uchun Post-response testlari

`GET {{baseUrl}}/booking/{{bookingId}}` uchun testlar yozing: status `200`; `Content-Type` JSON; `firstname` va `lastname` — bo'sh bo'lmagan string; `totalprice` — 0 dan katta yoki teng butun son; `checkout` sanasi `checkin` dan keyin.

<details>
<summary>💡 Javobni ko'rish</summary>

```javascript
pm.test("Status 200", () => {
  pm.response.to.have.status(200);
});

pm.test("JSON qaytdi", () => {
  pm.expect(pm.response.headers.get("Content-Type")).to.include(
    "application/json",
  );
});

const b = pm.response.json();

pm.test("Ism va familiya bo'sh emas", () => {
  pm.expect(b.firstname).to.be.a("string").and.not.be.empty;
  pm.expect(b.lastname).to.be.a("string").and.not.be.empty;
});

pm.test("Narx — manfiy bo'lmagan butun son", () => {
  pm.expect(Number.isInteger(b.totalprice)).to.be.true;
  pm.expect(b.totalprice).to.be.at.least(0);
});

pm.test("checkout checkin'dan keyin", () => {
  pm.expect(
    new Date(b.bookingdates.checkout) > new Date(b.bookingdates.checkin),
  ).to.be.true;
});
```

Eslatma: restful-booker manfiy narx va teskari sanalarni qabul qiladi — shuning uchun ba'zi booking'larda oxirgi ikki test **yiqiladi**. Bu test xatosi emas, API'dagi validatsiya yo'qligining isboti ([QA 11](/ios-lessons/qa-api-advanced)).

</details>

### Mashq 4: DemoBozor savatchasi uchun CRUD ketma-ketligi

Xayoliy DemoBozor API'sida savatcha elementlari uchun to'liq test ketma-ketligini (qadamlar va kutilgan kodlar) tuzing. Kamida bitta negative qadam qo'shing.

<details>
<summary>💡 Javobni ko'rish</summary>

| #   | So'rov                                                     | Kutilgan natija                                              |
| --- | ---------------------------------------------------------- | ------------------------------------------------------------ |
| 1   | `POST /api/v1/auth/verify` (telefon + OTP)                 | `200`, `accessToken` saqlanadi                               |
| 2   | `GET /api/v1/cart`                                         | `200`, savatcha bo'sh (yoki boshlang'ich holat qayd etiladi) |
| 3   | `POST /api/v1/cart/items` `{productId: 1042, quantity: 2}` | `201`, element ID saqlanadi                                  |
| 4   | `GET /api/v1/cart`                                         | `200`, element bor, `quantity = 2`, summa to'g'ri            |
| 5   | `PATCH /api/v1/cart/items/{id}` `{quantity: 99}`           | `200`, `quantity = 99` (yuqori chegara)                      |
| 6   | `PATCH ...` `{quantity: 100}`                              | `422` (yoki `400`), miqdor o'zgarmagan                       |
| 7   | `DELETE /api/v1/cart/items/{id}`                           | `204`                                                        |
| 8   | `GET /api/v1/cart`                                         | `200`, element yo'q                                          |
| 9   | Token'siz `GET /api/v1/cart`                               | `401`                                                        |

</details>

### Mashq 5: Promo-kod uchun data fayl

DemoBozor qoidasi: promo-kod 6–12 belgi, faqat A–Z va 0–9 (katta harf). Data-driven test uchun CSV tuzing: `code` va `expected` (`valid`/`invalid`) ustunlari, kamida 8 qator.

<details>
<summary>💡 Javobni ko'rish</summary>

```text
code,expected,izoh
ABC12,invalid,5 belgi (pastki chegaradan 1 kam)
ABC123,valid,6 belgi (pastki chegara)
ABCDEFGH1234,valid,12 belgi (yuqori chegara)
ABCDEFGH12345,invalid,13 belgi
abc123,invalid,kichik harf
ABC 123,invalid,probel
ABC-123,invalid,maxsus belgi
OʻZBEK1,invalid,lotin bo'lmagan harf (ʻ)
,invalid,bo'sh qiymat
```

Post-response'da `pm.iterationData.get("expected")` ga qarab kutilgan status kodni tanlaysiz (masalan, `valid` → `200`, `invalid` → `422`).

</details>

### Mashq 6: JWT'ni lokal oching

Toolshop API'dan token oling (`customer@practicesoftwaretesting.com` / `welcome01`) va uni lokal dekod qiling. `role` nima? Token necha soniya yashaydi? Shu ma'lumotdan kamida 2 ta test g'oyasi chiqaring.

<details>
<summary>💡 Javobni ko'rish</summary>

[7-bo'lim](#auth) dagi buyruqlar bilan: `role: 'user'`, `exp - iat = 300` soniya (5 daqiqa).

Test g'oyalari:

1. 5 daqiqadan keyin `GET /users/me` → `401` qaytishi kerak.
2. Token'siz va buzilgan token bilan `GET /users/me` → `401` (Toolshop'da tekshirilgan).
3. Oddiy foydalanuvchi token'i bilan admin endpoint'i (`GET /users`) → `403` (tekshirilgan).
4. Payload'dagi `role` ni `admin` ga o'zgartirib (signature eskicha qoladi) yuborsangiz → server rad etishi kerak (`401`). Buni faqat test muhitida va ruxsat bilan qiling.

</details>

---

## Xulosa {#xulosa}

- API — client va server o'rtasidagi shartnoma; bitta API bug'i web, iOS va Android'da birdaniga ko'rinadi.
- REST: resurslar (otlar) + method'lar (fe'llar). Method'ning safe/idempotent xususiyatlari qayta yuborish va dublikat buyurtma kabi real risklarni belgilaydi.
- Status kodni bilish — yarim jang: `401` vs `403`, `400` vs `422`, `500` deyarli har doim bug.
- JSON'da tip, `null`/yo'q maydon/bo'sh qator farqi va sana zonalari — eng ko'p bug manbalari.
- Base64 shifr emas; JWT payload'i ochiq o'qiladi — real token'larni onlayn dekoderlarga bermang.
- Postman: collection + environment + variable scope'lari + Pre-request/Post-response skriptlari = to'liq avtomatik API test; Newman uni terminal va CI'ga olib chiqadi.
- "Hujjat vs standart vs amaliyot" solishtiruvi — API'dagi topilmalarni to'g'ri tasniflash kaliti.

**Keyingi dars:** [QA 11: API testing (2) — Test dizayn, negative testlar, JSON Schema, Auth, Mock va Contract testing](/ios-lessons/qa-api-advanced)

## Manbalar {#manbalar}

1. [Roy Fielding — Representational State Transfer (REST), dissertatsiya 5-bob](https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)
2. [RFC 9110 — HTTP Semantics (method'lar, status kodlar)](https://www.rfc-editor.org/rfc/rfc9110.html)
3. [RFC 6585 — Additional HTTP Status Codes (429)](https://www.rfc-editor.org/rfc/rfc6585.html)
4. [MDN — HTTP request methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods)
5. [MDN — Idempotent](https://developer.mozilla.org/en-US/docs/Glossary/Idempotent)
6. [MDN — Safe (HTTP methods)](https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP)
7. [MDN — HTTP response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status)
8. [RFC 7519 — JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519.html)
9. [RFC 6749 — The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749.html)
10. [JWT.io — Introduction to JSON Web Tokens](https://jwt.io/introduction)
11. [OpenAPI Specification (latest)](https://spec.openapis.org/oas/latest.html)
12. [Swagger Petstore (v2)](https://petstore.swagger.io/)
13. [Swagger Petstore (OpenAPI 3)](https://petstore3.swagger.io/)
14. [Restful-booker API hujjati](https://restful-booker.herokuapp.com/apidoc/index.html)
15. [Restful-booker — GitHub](https://github.com/mwinteringham/restful-booker)
16. [Toolshop API hujjati (practicesoftwaretesting.com)](https://api.practicesoftwaretesting.com/api/documentation)
17. [Postman Docs — Use scripts to add logic and tests](https://learning.postman.com/docs/tests-and-scripts/write-scripts/intro-to-scripts/)
18. [Postman Docs — Write scripts to test API response data](https://learning.postman.com/docs/tests-and-scripts/write-scripts/test-scripts/)
19. [Postman Docs — Test script examples](https://learning.postman.com/docs/tests-and-scripts/write-scripts/test-examples/)
20. [Postman Docs — pm.test va pm.expect](https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-reference/pm-test-expect/)
21. [Postman Docs — Variables in scripts](https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-reference/pm-variables/)
22. [Postman Docs — pm.execution](https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-reference/pm-execution/)
23. [Postman Docs — Store and reuse values using variables](https://learning.postman.com/docs/sending-requests/variables/variables/)
24. [Postman Docs — Manage environments](https://learning.postman.com/docs/sending-requests/environments/managing-environments/)
25. [Postman Docs — Postman Vault](https://learning.postman.com/docs/sending-requests/postman-vault/postman-vault-secrets/)
26. [Postman Docs — Run collections (Collection Runner)](https://learning.postman.com/docs/collections/running-collections/intro-to-collection-runs/)
27. [Postman Docs — Run collections with data files](https://learning.postman.com/docs/collections/running-collections/working-with-data-files/)
28. [Postman Docs — Run collections using Newman CLI](https://learning.postman.com/docs/collections/using-newman-cli/command-line-integration-with-newman/)
29. [Postman Docs — About plans](https://learning.postman.com/docs/billing/about-plans)
30. [Postman Community — Pre-request va Post-response (sobiq Tests) haqida](https://community.postman.com/t/pre-script-and-post-script-test-option-are-not-displayed/64913)
31. [Newman — GitHub](https://github.com/postmanlabs/newman)
32. [Chai — BDD assertion API](https://www.chaijs.com/api/bdd/)
33. [Bruno](https://www.usebruno.com/)
34. [Hoppscotch](https://hoppscotch.io/)
35. [Insomnia](https://insomnia.rest/)
