2026-05-26 06:05:33 +00:00
|
|
|
|
# k6 API tests
|
|
|
|
|
|
|
2026-05-26 17:10:32 +00:00
|
|
|
|
完整的 Gateway API smoke + journey 測試套件。**所有對外端點**都至少在 `smoke/` 或 `journeys/` 裡有一發(含登入 MFA、忘記/改密碼、註冊 resume)。
|
2026-05-26 06:05:33 +00:00
|
|
|
|
|
|
|
|
|
|
## TL;DR
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
make k6-up # docker:mongo + redis + mailhog + postgres + zitadel
|
|
|
|
|
|
make k6-wait # 等 ZITADEL ready + 把 PAT 寫到 env file
|
|
|
|
|
|
make k6-gateway & # 起 gateway(背景;吃 etc/gateway.k6.yaml)
|
|
|
|
|
|
make k6-seed-admin # (rbac journey 才需要)seed tenant_admin user
|
|
|
|
|
|
make k6-all # 跑 smoke + journey
|
|
|
|
|
|
make k6-down # 清掉
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
單跑一個檔(記得先載入環境變數):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
source deploy/zitadel/machinekey/k6.env
|
|
|
|
|
|
k6 run test/k6/smoke/health.js
|
|
|
|
|
|
k6 run test/k6/journeys/email_register_full.js
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 環境變數
|
|
|
|
|
|
|
|
|
|
|
|
| 變數 | 預設 | 說明 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `BASE_URL` | `http://localhost:8888` | Gateway base URL |
|
|
|
|
|
|
| `MAILHOG_URL` | `http://localhost:8025` | MailHog HTTP API(撈 email OTP) |
|
|
|
|
|
|
| `REDIS_ADDR` | `localhost:6379` | Redis 位址(撈 SMS OTP) |
|
|
|
|
|
|
| `TENANT_SLUG` | `k6-tenant` | register payload tenant |
|
|
|
|
|
|
| `INVITE_CODE` | `K6INVITE` | tenant 開啟 invite 時用 |
|
|
|
|
|
|
| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | — | rbac journey seeded admin |
|
|
|
|
|
|
| `OTP_POLL_INTERVAL_MS` | `300` | OTP poll 頻率 |
|
|
|
|
|
|
| `OTP_POLL_TIMEOUT_MS` | `5000` | OTP poll 超時 |
|
2026-05-26 17:10:32 +00:00
|
|
|
|
| `RESEND_COOLDOWN_SECONDS` | `60` | 與 `gateway.k6.yaml` OTP 重送冷卻一致(cooldown smoke 會 sleep) |
|
2026-05-26 06:05:33 +00:00
|
|
|
|
|
|
|
|
|
|
## 目錄結構
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
test/k6/
|
|
|
|
|
|
├── README.md
|
|
|
|
|
|
├── lib/ # 共用 helper
|
|
|
|
|
|
│ ├── config.js # 環境變數 + unique() + SUCCESS_CODE
|
|
|
|
|
|
│ ├── http.js # get/post/...、checkEnvelope、withBearer
|
|
|
|
|
|
│ ├── otp.js # fetchEmailOTP (MailHog) / fetchSMSOTP (Redis)
|
|
|
|
|
|
│ ├── totp.js # HMAC-SHA1 TOTP(P4)
|
2026-05-26 17:10:32 +00:00
|
|
|
|
│ ├── auth.js # register / confirm / login / MFA / password helper
|
|
|
|
|
|
│ ├── member.js # TOTP enroll / change password helper
|
2026-05-26 06:05:33 +00:00
|
|
|
|
│ └── seed.js # tenant + invite + admin role bootstrap(P5)
|
|
|
|
|
|
├── smoke/ # 每個端點至少一發
|
|
|
|
|
|
│ ├── health.js # GET /api/v1/health
|
|
|
|
|
|
│ ├── auth_public.js # register / login / refresh / social-start (+negative)
|
2026-05-26 17:10:32 +00:00
|
|
|
|
│ ├── auth_register_resume.js # register/resume(happy + 404/409/429/400)
|
|
|
|
|
|
│ ├── auth_password.js # password/forgot + reset(happy + 各種 negative)
|
|
|
|
|
|
│ ├── auth_login_mfa.js # login/mfa(MFA 前置 + negative)
|
2026-05-26 06:05:33 +00:00
|
|
|
|
│ ├── auth_bearer.js # logout
|
2026-05-26 17:10:32 +00:00
|
|
|
|
│ ├── member.js # me / patch / verify / TOTP / change-password negative
|
2026-05-26 06:05:33 +00:00
|
|
|
|
│ ├── permission_read.js # catalog / me
|
|
|
|
|
|
│ └── permission_admin.js # roles CRUD / role-permissions / user-roles / mappings / policy reload
|
|
|
|
|
|
└── journeys/ # 完整流程
|
|
|
|
|
|
├── email_register_full.js # register → confirm OTP(MailHog) → me → patch → logout
|
2026-05-26 17:10:32 +00:00
|
|
|
|
├── register_resume_full.js # register → resume → confirm → me
|
|
|
|
|
|
├── password_forgot_reset_full.js # forgot → reset → login(新密碼)
|
|
|
|
|
|
├── login_mfa_full.js # enroll TOTP → login MFA → me
|
|
|
|
|
|
├── change_password_full.js # change password → login(新密碼)
|
2026-05-26 06:05:33 +00:00
|
|
|
|
├── login_refresh.js # login → refresh → me → logout
|
|
|
|
|
|
├── email_verify.js # register → confirm → email verify start → confirm
|
|
|
|
|
|
├── phone_verify.js # register → confirm → phone verify (Redis OTP)
|
|
|
|
|
|
├── totp_full.js # enroll → confirm → verify → backup-codes → disable
|
|
|
|
|
|
├── rbac_admin.js # role CRUD → assign → policy reload → me/permissions
|
|
|
|
|
|
└── token_exchange.js # ZITADEL id_token → CloudEP JWT
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## OTP 怎麼撈
|
|
|
|
|
|
|
|
|
|
|
|
- **Email** → `make k6-up` 含 MailHog(:1025 SMTP / :8025 HTTP API)。Gateway 把 OTP 寄到 MailHog;k6 透過 `/api/v2/search?kind=to&query=<email>` 撈到信件,從 body 抓 6 位數。
|
|
|
|
|
|
- **SMS** → mock provider 把 body 寫到 `dev:notification:last:sms:<phone>`(見 [mock_sender.go](../../internal/model/notification/provider/sms/mock_sender.go) `WithMockRedis`),k6 用 `k6/experimental/redis` 直接讀。
|
|
|
|
|
|
|
|
|
|
|
|
兩者都有 `OTP_POLL_TIMEOUT_MS`(預設 5 秒)保護,超時直接 fail。
|
|
|
|
|
|
|
|
|
|
|
|
## 覆蓋率(39 endpoints)
|
|
|
|
|
|
|
|
|
|
|
|
| 模組 | 端點 | 覆蓋 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| Normal | `GET /api/v1/health` | smoke/health |
|
|
|
|
|
|
| Auth 公開 | `POST /register` | journeys/email_register_full + smoke/auth_public |
|
|
|
|
|
|
| Auth 公開 | `POST /register/confirm` | journeys/email_register_full |
|
|
|
|
|
|
| Auth 公開 | `POST /register/resend` | smoke/auth_public |
|
2026-05-26 17:10:32 +00:00
|
|
|
|
| Auth 公開 | `POST /register/resume` | smoke/auth_register_resume + journeys/register_resume_full |
|
|
|
|
|
|
| Auth 公開 | `POST /password/forgot` | smoke/auth_password + journeys/password_forgot_reset_full |
|
|
|
|
|
|
| Auth 公開 | `POST /password/reset` | smoke/auth_password + journeys/password_forgot_reset_full |
|
|
|
|
|
|
| Auth 公開 | `POST /login/mfa` | smoke/auth_login_mfa + journeys/login_mfa_full |
|
2026-05-26 06:05:33 +00:00
|
|
|
|
| Auth 公開 | `POST /register/social/start` | smoke/auth_public (happy) |
|
|
|
|
|
|
| Auth 公開 | `GET /register/social/callback` | smoke/auth_public (negative — TODO happy) |
|
|
|
|
|
|
| Auth 公開 | `POST /login` | journeys/login_refresh + smoke/auth_public (negative) |
|
|
|
|
|
|
| Auth 公開 | `POST /token/refresh` | journeys/login_refresh + smoke/auth_public (negative) |
|
|
|
|
|
|
| Auth 公開 | `POST /token/exchange` | journeys/token_exchange (negative — TODO happy) |
|
|
|
|
|
|
| Auth 公開 | `POST /login/social/start` | smoke/auth_public (happy) |
|
|
|
|
|
|
| Auth 公開 | `GET /login/social/callback` | smoke/auth_public (negative — TODO happy) |
|
|
|
|
|
|
| Auth Bearer | `POST /logout` | smoke/auth_bearer + journeys/email_register_full |
|
|
|
|
|
|
| Member | `GET /me` | smoke/member + all journeys |
|
|
|
|
|
|
| Member | `PATCH /me` | smoke/member + journeys/email_register_full |
|
|
|
|
|
|
| Member | `POST /me/verifications/email/start` | smoke/member + journeys/email_verify |
|
|
|
|
|
|
| Member | `POST /me/verifications/email/confirm` | smoke/member (negative) + journeys/email_verify (happy) |
|
|
|
|
|
|
| Member | `POST /me/verifications/phone/start` | smoke/member + journeys/phone_verify |
|
|
|
|
|
|
| Member | `POST /me/verifications/phone/confirm` | smoke/member (negative) + journeys/phone_verify (happy) |
|
|
|
|
|
|
| Member | `GET /me/totp` | smoke/member + journeys/totp_full |
|
|
|
|
|
|
| Member | `POST /me/totp/enroll-start` | smoke/member + journeys/totp_full |
|
|
|
|
|
|
| Member | `POST /me/totp/enroll-confirm` | smoke/member (negative) + journeys/totp_full (happy) |
|
|
|
|
|
|
| Member | `POST /me/totp/verify` | smoke/member (negative) + journeys/totp_full (happy) |
|
|
|
|
|
|
| Member | `POST /me/totp/backup-codes` | smoke/member (negative) + journeys/totp_full (happy) |
|
|
|
|
|
|
| Member | `DELETE /me/totp` | smoke/member + journeys/totp_full |
|
2026-05-26 17:10:32 +00:00
|
|
|
|
| Member | `POST /me/password` | smoke/member (negative) + journeys/change_password_full |
|
2026-05-26 06:05:33 +00:00
|
|
|
|
| Perm 讀 | `GET /permissions/catalog` | smoke/permission_read + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 讀 | `GET /permissions/me` | smoke/permission_read + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `GET /roles` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `POST /roles` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `PATCH /roles/:id` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `DELETE /roles/:id` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `GET /roles/:id/permissions` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `PUT /roles/:id/permissions` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `GET /users/:uid/roles` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `POST /users/:uid/roles` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `DELETE /users/:uid/roles/:role_id` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
| Perm 管理 | `GET /role-mappings` | smoke/permission_admin |
|
|
|
|
|
|
| Perm 管理 | `PUT /role-mappings` | smoke/permission_admin |
|
|
|
|
|
|
| Perm 管理 | `DELETE /role-mappings` | smoke/permission_admin |
|
|
|
|
|
|
| Perm 管理 | `POST /policy/reload` | smoke/permission_admin + journeys/rbac_admin |
|
|
|
|
|
|
|
|
|
|
|
|
## RBAC 系列(需 admin seed)
|
|
|
|
|
|
|
|
|
|
|
|
`journeys/rbac_admin.js` 需要 `ADMIN_EMAIL` / `ADMIN_PASSWORD` 環境變數。`make k6-seed-admin`
|
|
|
|
|
|
會跑 [cmd/k6-seed-admin](../../cmd/k6-seed-admin/main.go):
|
|
|
|
|
|
|
|
|
|
|
|
1. 用 API 註冊一個固定的 `k6-admin@k6.local`
|
|
|
|
|
|
2. 從 MailHog 撈 OTP 完成 confirm
|
|
|
|
|
|
3. 寫入 permission catalog + 預設 system roles(透過 `internal/model/permission/seed`)
|
|
|
|
|
|
4. 指派 `tenant_admin` 給該 UID
|
|
|
|
|
|
5. 把 `ADMIN_EMAIL / ADMIN_PASSWORD / ADMIN_UID` 寫到 `k6.env`
|
|
|
|
|
|
|
|
|
|
|
|
`k6-seed-admin` 是冪等的,重跑沒事。
|
|
|
|
|
|
|
|
|
|
|
|
沒跑 seed 時,`rbac_admin.js` 會印 skip notice 並 exit 0(admin endpoint 的路由存在性
|
|
|
|
|
|
已由 `smoke/permission_admin.js` 涵蓋)。
|
|
|
|
|
|
|
|
|
|
|
|
## 已知無法純 k6 跑的
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /api/v1/auth/register/social/callback` 與 `GET /api/v1/auth/login/social/callback` happy path 需要 Google OAuth UI 跳轉,純 k6 無法走完。Smoke 只覆蓋 negative(無效 state → 400)。
|
|
|
|
|
|
- `POST /api/v1/auth/token/exchange` happy path 需要一個來自 ZITADEL 的有效 `id_token`;目前 `make k6-up` 的 ZITADEL bootstrap 只建 service account PAT,沒有開 OIDC client password grant,因此 happy 路徑 TODO,smoke 只跑 negative。要開:在 `deploy/zitadel/steps.yaml` 加 Application + password grant,再用 `/oauth/v2/token` 拿 id_token。
|
|
|
|
|
|
|
|
|
|
|
|
## 不要 commit 的東西
|
|
|
|
|
|
|
|
|
|
|
|
- `deploy/zitadel/machinekey/zitadel-admin-sa.token` / `.json` 已在 `.gitignore`。
|
|
|
|
|
|
- `etc/gateway.k6.yaml` 內的 secret 都是固定 dev 值,本機 / CI 可用,**勿** 上 prod。
|