2026-05-21 23:52:39 +00:00
|
|
|
|
//go:build e2e
|
|
|
|
|
|
|
|
|
|
|
|
package e2e
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// TestZZZ_AuthTokenRefreshAndLogout runs last (separate go test invocation).
|
|
|
|
|
|
// It uses an isolated refresh so seed tokens used by member/permission stay valid.
|
|
|
|
|
|
func TestZZZ_AuthTokenRefreshAndLogout(t *testing.T) {
|
2026-05-22 09:18:36 +00:00
|
|
|
|
e2eStep(t, "A-10/A-11", "POST", "/api/v1/auth/{token/refresh,logout}", "刷新 token → 用新 access 打 /me → logout → 黑名單後再打 /me=401")
|
2026-05-21 23:52:39 +00:00
|
|
|
|
c := isolatedAuthClient(t)
|
|
|
|
|
|
|
|
|
|
|
|
refreshEnv := 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(refreshEnv.Data, &pair))
|
|
|
|
|
|
require.Equal(t, c.Fixture.UID, pair.UID)
|
|
|
|
|
|
|
|
|
|
|
|
c.Fixture.AccessToken = pair.AccessToken
|
|
|
|
|
|
c.Fixture.RefreshToken = pair.RefreshToken
|
|
|
|
|
|
|
|
|
|
|
|
c.DoExpectOK(t, http.MethodGet, "/api/v1/members/me", nil, true)
|
|
|
|
|
|
c.DoExpectOK(t, http.MethodPost, "/api/v1/auth/logout", nil, true)
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestAuth_MissingBearer_401(t *testing.T) {
|
2026-05-22 09:18:36 +00:00
|
|
|
|
e2eStep(t, "A-12", "GET", "/api/v1/members/me", "未帶 Bearer → 401(AuthJWT middleware)")
|
2026-05-21 23:52:39 +00:00
|
|
|
|
c := NewClient(t)
|
|
|
|
|
|
resp, env := c.Do(t, http.MethodGet, "/api/v1/members/me", nil, false)
|
|
|
|
|
|
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
|
|
|
|
|
require.NotEqual(t, int64(successCode), env.Code)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestAuth_PublicValidationErrors(t *testing.T) {
|
2026-05-22 09:18:36 +00:00
|
|
|
|
e2eStep(t, "A-13", "POST", "/api/v1/auth/*", "公開 Auth 端點輸入驗證錯誤 → 400 + Facade scope")
|
2026-05-21 23:52:39 +00:00
|
|
|
|
c := NewClient(t)
|
|
|
|
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
path string
|
|
|
|
|
|
body any
|
|
|
|
|
|
}{
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "register missing required fields",
|
|
|
|
|
|
path: "/api/v1/auth/register",
|
|
|
|
|
|
body: map[string]any{},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "login invalid email and password",
|
|
|
|
|
|
path: "/api/v1/auth/login",
|
|
|
|
|
|
body: map[string]any{
|
|
|
|
|
|
"tenant_slug": c.Fixture.TenantSlug,
|
|
|
|
|
|
"email": "not-an-email",
|
|
|
|
|
|
"password": "short",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "token refresh missing token",
|
|
|
|
|
|
path: "/api/v1/auth/token/refresh",
|
|
|
|
|
|
body: map[string]any{},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "social login invalid provider",
|
|
|
|
|
|
path: "/api/v1/auth/login/social/start",
|
|
|
|
|
|
body: map[string]any{
|
|
|
|
|
|
"tenant_slug": c.Fixture.TenantSlug,
|
|
|
|
|
|
"provider": "github",
|
|
|
|
|
|
"redirect_uri": "http://127.0.0.1/callback",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
|
env := c.DoExpectHTTP(t, http.MethodPost, tc.path, tc.body, false, http.StatusBadRequest)
|
|
|
|
|
|
require.NotEqual(t, int64(successCode), env.Code)
|
2026-05-22 09:18:36 +00:00
|
|
|
|
// Facade scope 10101000 = InputInvalidFormat(gateway parse / validate 進入點)
|
|
|
|
|
|
require.Equal(t, int64(10), env.Code/1_000_000, "expected Facade scope, got code=%d", env.Code)
|
2026-05-21 23:52:39 +00:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|