# Test

> **TL;DR** — Không có sandbox: test gọi `https://www.wandura.ai`. Giữ lưu lượng test trong **tổ chức test** của bạn (chỉ phim mẫu, lượt phát không bao giờ được tính) và tạo bất kỳ lỗi nào khi cần bằng header `Wandura-Simulate`. Chạy CI của bạn với một mock dựng từ tệp OpenAPI. Không bao giờ load-test production. Sau đó chạy test plan bên dưới trên điện thoại thật.

## Test ở đâu
| Tầng | Dùng | Có tính là lượt xem? |
|---|---|---|
| Unit test và CI | Một mock server dựng từ tệp OpenAPI, ví dụ `prism mock https://docs.wandura.ai/developers/openapi/delivery-v1.json`, hoặc các bản giả trong [ví dụ](https://docs.wandura.ai/vi/v1/developers/guides/catalog-sync#code) | Không (không gì tới Wandura) |
| Integration test | Key của **tổ chức test**, gọi production | Không |
| Nghiệm thu trên điện thoại thật | Tổ chức test, rồi một lượt kiểm cuối ở production | Production: có |

## Tổ chức test
Mỗi đối tác có một **tổ chức test** bên cạnh tổ chức production. Wandura tạo nó khi bạn đề nghị trong kênh Discord của team và thêm Admin, Developer của bạn vào. Trên partner.wandura.ai, chuyển sang "*Tổ chức của bạn* (test)" rồi tạo key ở **Developers** như thường lệ. Các ví dụ trên trang này gọi key đó là `WANDURA_TEST_KEY`.

Key test gọi đúng URL production, `https://www.wandura.ai`, và chạy y như key production, chỉ khác ba điểm:

1. **Không được tính.** Lượt phát của nó không bao giờ được tính là lượt xem, usage hay doanh thu.
2. **Chỉ phim mẫu.** Nó chỉ phát được hai phim mẫu của Wandura là `ninety-days` và `the-fire-clause` (tập ngắn, có phụ đề tiếng Anh). Tổ chức test không có video mở.
3. **Giả lập lỗi.** Nó được gửi header `Wandura-Simulate` (bên dưới). Key production bỏ qua hoàn toàn header này.

`GET /config` cho biết bạn đang cầm loại key nào:

```json
{
  "key": { "scopes": ["metadata:read", "playback"], "test_organisation": true }
}
```

**Trạng thái thật, khi bạn đề nghị.** Với những gì giả lập không cho thấy được (ví dụ một key thật sự ngừng chạy, hay một giấy phép thật sự kết thúc), hãy hỏi trong kênh của bạn: Wandura có thể treo tổ chức test, treo hoặc kết thúc giấy phép của một phim mẫu, tắt streaming, hoặc hạ rate limit playback để bạn test `429` thật. Thay đổi có hiệu lực trong vòng 30 giây. Rate limit playback của tổ chức test là 600 vé mỗi phút.

## Lỗi giả lập
Với key test, gửi `Wandura-Simulate: <scenario>`. Bạn nhận đúng status, body và header như lỗi thật, kèm `Wandura-Simulated: <scenario>` trên response. Câu trả lời giả lập không cấp vé và không tính vào rate limit; bản thân các câu trả lời giả lập bị giới hạn 300 lượt mỗi phút cho mỗi tổ chức. Bước kiểm key, scope và phiên bản vẫn chạy trước, nên một key sai vẫn nhận `401`. Nếu response không có header `Wandura-Simulated`, nghĩa là không có gì được giả lập (tên lạ, hoặc kịch bản không áp dụng cho endpoint đó) và request đã chạy bình thường.

| Kịch bản | Endpoint | Bạn nhận |
|---|---|---|
| `rate_limited` | playback, catalog, config | `429` `rate_limited` kèm `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Window` |
| `prefetch_shed` | playback | `429` `rate_limited`, `"reason": "prefetch_shed"` |
| `service_unavailable` | playback, catalog, config | `503` `service_unavailable`, `Retry-After: 1` |
| `service_unavailable:provider` | playback | `503` `service_unavailable` (nhà cung cấp video), `Retry-After: 2` |
| `service_unavailable:catalog` | catalog | `503` `service_unavailable` (`Could not read the catalog in time — retry`) |
| `server_error` | playback, catalog, config | `500` với body JSON có `request_id`, như một lỗi bất ngờ thật |
| `unsupported_version` | playback, catalog, config | `400` `unsupported_version` kèm `supported_versions`, `latest_version`, `upgrade_to` |
| `not_entitled` | playback | `403` `not_entitled`, `reason` `entitlement_not_found` |
| `not_entitled:<reason>` | playback | `403` `not_entitled` với `reason` là `entitlement_suspended`, `streaming_disabled`, `outside_window` hoặc `org_suspended` |
| `org_suspended` | playback, catalog, config | Playback: như `not_entitled:org_suspended`; catalog và config: `403` với `error` là `org_suspended` |
| `not_ready` | playback | `404` `not_ready` |
| `not_ready:no_rendition` | playback | `503` `not_ready` |
| `sdk_required` | playback | `409` `sdk_required` kèm `protection` |
| `partial_batch` | playback, lô 2–3 tập | Phần tử 0 là vé **thật** (không được tính, như mọi vé của tổ chức test); các phần tử sau là `503` với `retry_after: 2`, hoặc `429` `prefetch_shed` |
| `empty_catalog` | catalog | `200` với `films` và `episodes` rỗng |
| `version_deprecated` | catalog, config | Một `200` thật, kèm header `Deprecation`, `Sunset` và `Link` |

Trong một lô, kịch bản theo từng tập trả `200` `{ "items": [...] }` với lỗi trên từng phần tử, y như một lô thật.

Không giả lập được: giấy phép Player SDK hết hạn (nó nằm trên thiết bị).

### Ví dụ: `rate_limited`
```bash
curl -s -i https://www.wandura.ai/api/delivery/v1/catalog \
  -H "Authorization: Bearer $WANDURA_TEST_KEY" \
  -H "Wandura-Simulate: rate_limited"
```

```text
HTTP/2 429
retry-after: 1
x-ratelimit-limit: 600
x-ratelimit-window: 60
wandura-simulated: rate_limited
```

```json
{
  "error": "Too many catalog requests for this account. This is an abuse ceiling, not a quota — if a normal integration reaches it, contact us and it will be raised.",
  "error_code": "rate_limited"
}
```

### Ví dụ: `not_entitled:outside_window`
Không cần giá trị thiết bị: không có gì được cấp.

```bash
curl -s https://www.wandura.ai/api/delivery/v1/playback \
  -H "Authorization: Bearer $WANDURA_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Wandura-Simulate: not_entitled:outside_window" \
  -d '{ "film_ref": "ninety-days", "episode_no": 1, "intent": "play" }'
```

```json
{
  "error": "outside_window",
  "error_code": "not_entitled",
  "reason": "outside_window"
}
```

### Ví dụ: `partial_batch`
Phần tử 0 là vé thật, nên hãy gửi giá trị thiết bị của một máy thật (xem [Bắt đầu nhanh · Phần B](https://docs.wandura.ai/vi/v1/developers/quickstart#part-b)).

```bash
curl -s https://www.wandura.ai/api/delivery/v1/playback \
  -H "Authorization: Bearer $WANDURA_TEST_KEY" \
  -H "Content-Type: application/json" \
  -H "Wandura-Simulate: partial_batch" \
  -d '{ "film_ref": "ninety-days", "episode_no": 1, "next": 2, "intent": "play",
        "union_info": "'"$UNION_INFO"'", "drm_kek": "'"$DRM_KEK"'", "viewer_ref": "qa-viewer-1:device-1" }'
```

```json
{
  "items": [
    { "request": { "film_ref": "ninety-days", "episode_no": 1 }, "status": 200, "vid": "v0d2f8c1a9e4b7", "play_auth_token": "placeholder-play-token", "expires_at": "2026-10-02T12:30:00.000Z", "protection": "private-encrypted", "subtitles": [] },
    { "request": { "film_ref": "ninety-days", "episode_no": 2 }, "status": 503, "error": "The video provider did not answer in time — retry", "error_code": "service_unavailable", "retry_after": 2 },
    { "request": { "film_ref": "ninety-days", "episode_no": 3 }, "status": 429, "error": "Prefetch requests are paused while your playback traffic is close to its ceiling — tickets for viewers who pressed play are still issued. Retry the prefetch after Retry-After, or request it when the viewer plays.", "error_code": "rate_limited", "reason": "prefetch_shed", "retry_after": 1 }
  ]
}
```

## Những gì bạn tái hiện được với key bất kỳ
| Kịch bản | Cách làm |
|---|---|
| `401` | Không có key, hoặc key đã bị thu hồi (bị từ chối khoảng một giây sau khi thu hồi). |
| `403` scope | Một key chỉ có `metadata:read`, gọi `POST /playback`. |
| `400` `invalid_device_info` | Giá trị thiết bị là `"(null)"` hoặc ngắn hơn 8 ký tự. |
| `400` `invalid_request` | Chỉ có một giá trị thiết bị, hoặc một lô sai định dạng. |
| `409` `sdk_required` | Không có giá trị thiết bị nào. |
| `404` `not_ready` | `episode_id` không tồn tại, hoặc `next: 2` ở tập cuối. |
| `400` `unsupported_version` | `Wandura-Version: 1999-01-01`. |
| Creative `404` `4040`, `422` | ID không tồn tại; `limit` hoặc `cursor` sai. |
| Vé hết hạn | Tạm dừng playback hơn 30 phút. |

## Load test
Đừng load-test production. Để test hệ thống của chính bạn khi tải cao, hãy mock Wandura. Để test cách xử lý `429` với Wandura, dùng `Wandura-Simulate: rate_limited`, hoặc đề nghị hạ rate limit cho tổ chức test của bạn.

## Test plan
Chạy trên bản build release, trên một iPhone thật và một điện thoại Android thật.

**Key và bảo mật**

- [ ] Không có `wnd_live_` trong các app đã build.
- [ ] Không có key đầy đủ, `play_auth_token` hay giá trị thiết bị thô trong log của bạn ([Quan sát hệ thống](https://docs.wandura.ai/vi/v1/developers/production/observability#never-log)).
- [ ] Catalog không có key: `401`; với key thiếu `metadata:read`: `403`. Job của bạn cảnh báo và giữ nguyên bản sao.

**Catalog**

- [ ] Các phim hiển thị khớp với giấy phép Wandura đã xác nhận, theo thứ tự của API.
- [ ] Tập miễn phí mở được mà không phải trả tiền; tập kế tiếp hiện paywall của bạn.
- [ ] Tập giữ chỗ hiện "sắp ra mắt" và không chạm được; tập `stream_ready: false` bị làm mờ.
- [ ] Khi chặn mạng của sync job trong một lần chạy, catalog trong app không đổi.

**Playback**

- [ ] Tập miễn phí phát được, có phụ đề; đổi ngôn ngữ hoạt động; tập không có phụ đề vẫn phát.
- [ ] Tập bị khoá hiện paywall của bạn và không tạo request nào tới Wandura.
- [ ] Bắt đầu một tập gửi một request (`play` + các prefetch đã mở khoá); chuyển sang tập kế thì phát ngay và gửi một `play` mới.
- [ ] Tạm dừng hơn 30 phút rồi phát tiếp dẫn tới đúng một vé mới và phát lại từ cùng vị trí.
- [ ] Bật chế độ máy bay giữa tập rồi tắt: phục hồi mà không crash.
- [ ] `429`, `503`, `500`, `not_entitled`, `not_ready` và `partial_batch`, giả lập bằng `Wandura-Simulate`, đều hiện đúng màn hình.

**Bản quyền kết thúc**

- [ ] Nhờ Wandura kết thúc giấy phép của một phim mẫu trong tổ chức test của bạn. Sau hai lần đồng bộ kế tiếp, phim bị ẩn, và request xin vé trả `403` `not_entitled`.

## Liên quan
- [Checklist go-live](https://docs.wandura.ai/vi/v1/developers/production/go-live)
- [Lỗi](https://docs.wandura.ai/vi/v1/developers/concepts/errors)
