template-monorepo/test/e2e/journey_session_test.go

56 lines
2.0 KiB
Go
Raw Normal View History

test(e2e): 加 banner / e2e-list / k6 風格 user journey 讓「我有哪些測試、現在在測什麼」一眼看得到,並補上跨 endpoint 的狀態流測試: 每個測試開頭印中文 banner - 新增 e2eStep(t, id, method, path, desc) helper(test/e2e/setup_test.go) - 17 個 contract test 開頭加 banner,go test -v 會逐個顯示 ▶ [M-01] GET /api/v1/members/me — 讀 profile(tenant/uid/status) - 對外 ID 與 docs/e2e-testing.md 的測試覆蓋矩陣對齊 新增 make e2e-list - scripts/e2e-list.sh 掃 _test.go,分兩節印 contract tests + journeys; 每個 journey 列出所有 step ID + 描述(Step 用 ▶、SkipStep 用 ⊘) scripts 彩色 step banner + optional MailHog - scripts/e2e-lib.sh 抽共用 helpers(e2e_step/info/ok/warn、e2e_print_services) - e2e-run.sh / e2e-up.sh 改用 step banner + 服務面板(執行完印出 Mongo/Redis/ Gateway/MailHog 的 URL) - E2E_WITH_SMTP=1 會額外起 MailHog(http://localhost:8025),方便肉眼確認流程 k6 風格 user journey - 新增 test/e2e/journey.go:NewJourney + Step + SkipStep + Summary, 任一步 fail 自動 skip 後續,輸出 ▶ [J-x.y] 階層 banner - J-1 Tenant Owner 入職第一天(12 steps):/me → PATCH → email verify → phone verify → TOTP enroll/verify/replay/disable - J-2 Tenant Admin 建 qa_engineer 角色 → 指派 → 二人視角驗證 → 撤銷(8 steps) - J-3 Session 生命週期 refresh → /me → logout → 舊 token 401(4 steps,ZZZ 排最後) - J-4 完整註冊 → 登入(5 steps stub,標 SkipStep;接 ZITADEL container 後改 Step 即可) - make e2e-journey / make test-e2e-journey 拆獨立 target;e2e-run.sh 透過 E2E_MODE=journey + E2E_TEST_PATTERN_ZZZ 切換 docs/e2e-testing.md - 首節改為「我現在有哪些測試?make e2e-list」並附 banner 範例輸出 - 加 Journeys 章節:journey 列表、執行範例、失敗時的輸出、寫新 journey 範本 - 補 e2e-journey / test-e2e-journey / E2E_WITH_SMTP 環境變數 Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 09:18:36 +00:00
//go:build e2e
package e2e
import (
"encoding/json"
"net/http"
"testing"
"github.com/stretchr/testify/require"
)
// TestZZZJourney_SessionLifecycle 走 JWT 的完整生命週期:
// refresh 取得新 pair → 用新 access 打 /me 成功 → logout → 同一 access 再打 = 401
//
// 與 TestZZZ_AuthTokenRefreshAndLogout 同樣放在 ZZZ 區段最後跑(會撤銷 JWT
// 用 isolatedAuthClient 確保不汙染 member / permission journey 使用的 seed token。
func TestZZZJourney_SessionLifecycle(t *testing.T) {
j := NewJourney(t, "J-3", "Session 生命週期refresh → /me → logout → 舊 token 401")
defer j.Summary()
c := isolatedAuthClient(t)
j.Step("1", "POST /auth/token/refresh — 用 isolated refresh token 取得新 access/refresh pair", func(t *testing.T) {
env := c.DoExpectOK(t, http.MethodPost, "/api/v1/auth/token/refresh", map[string]string{
"refresh_token": c.Fixture.RefreshToken,
}, false)
var pair struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
UID string `json:"uid"`
}
require.NoError(t, json.Unmarshal(env.Data, &pair))
require.NotEmpty(t, pair.AccessToken)
require.NotEmpty(t, pair.RefreshToken)
require.Equal(t, c.Fixture.UID, pair.UID)
c.Fixture.AccessToken = pair.AccessToken
c.Fixture.RefreshToken = pair.RefreshToken
})
j.Step("2", "GET /members/me — 用 refresh 完拿到的新 access 打 /me 應該成功", func(t *testing.T) {
c.DoExpectOK(t, http.MethodGet, "/api/v1/members/me", nil, true)
})
j.Step("3", "POST /auth/logout — 把目前 jti 加入黑名單", func(t *testing.T) {
c.DoExpectOK(t, http.MethodPost, "/api/v1/auth/logout", nil, true)
})
j.Step("4", "GET /members/me — 同一 access token 再打應該 401jti blacklisted", func(t *testing.T) {
resp, env := c.Do(t, http.MethodGet, "/api/v1/members/me", nil, true)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
require.NotEqual(t, int64(successCode), env.Code)
})
}