Files
cms-server/api/auth/auth_handler_test.go
Asai Neko 82c412d839
All checks were successful
Server Check Build (NixCN CMS) TeamCity build finished
Add more tests for modules co worked by claude
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-03-26 23:36:40 +08:00

252 lines
7.3 KiB
Go

package auth
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"nixcn-cms/service/service_auth"
"nixcn-cms/testutil"
)
func init() { gin.SetMode(gin.TestMode) }
func setupAuthRouter(t *testing.T) *gin.Engine {
t.Helper()
testutil.Setup(t)
testutil.SeedClient(t)
r := gin.New()
ApiHandler(r.Group("/auth"))
return r
}
func postJSON(t *testing.T, r *gin.Engine, path string, body any) *httptest.ResponseRecorder {
t.Helper()
b, _ := json.Marshal(body)
req := httptest.NewRequest(http.MethodPost, path, bytes.NewBuffer(b))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
func getRequest(t *testing.T, r *gin.Engine, path string) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(http.MethodGet, path, nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
func postWithBearer(t *testing.T, r *gin.Engine, path, token string, body any) *httptest.ResponseRecorder {
t.Helper()
var bodyReader io.Reader
if body != nil {
b, _ := json.Marshal(body)
bodyReader = bytes.NewBuffer(b)
}
req := httptest.NewRequest(http.MethodPost, path, bodyReader)
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
func extractQueryParam(t *testing.T, rawURL, param string) string {
t.Helper()
u, err := url.Parse(rawURL)
require.NoError(t, err)
return u.Query().Get(param)
}
// ---- Magic ----
func TestMagicHandlerSuccess(t *testing.T) {
r := setupAuthRouter(t)
w := postJSON(t, r, "/auth/magic", service_auth.MagicData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Email: "handler@example.com",
})
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
data, ok := resp["data"].(map[string]any)
require.True(t, ok)
assert.NotEmpty(t, data["uri"])
}
func TestMagicHandlerInvalidJSON(t *testing.T) {
r := setupAuthRouter(t)
req := httptest.NewRequest(http.MethodPost, "/auth/magic", bytes.NewBufferString("{invalid"))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ---- Redirect ----
func TestRedirectHandlerMissingParams(t *testing.T) {
r := setupAuthRouter(t)
w := getRequest(t, r, "/auth/redirect")
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestRedirectHandlerInvalidCode(t *testing.T) {
r := setupAuthRouter(t)
w := getRequest(t, r, "/auth/redirect?client_id="+testutil.TestClientID+
"&redirect_uri=http://localhost/callback&code=bad-code&state=s")
assert.Equal(t, http.StatusForbidden, w.Code)
}
// ---- Token ----
func TestTokenHandlerInvalidCode(t *testing.T) {
r := setupAuthRouter(t)
w := postJSON(t, r, "/auth/token", service_auth.TokenData{Code: "invalid"})
assert.Equal(t, http.StatusForbidden, w.Code)
}
// ---- Refresh ----
func TestRefreshHandlerInvalidToken(t *testing.T) {
r := setupAuthRouter(t)
w := postJSON(t, r, "/auth/refresh", service_auth.RefreshData{RefreshToken: "bad"})
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
// ---- Full flow: Magic → Redirect → Token → Refresh ----
func TestAuthFullFlow(t *testing.T) {
r := setupAuthRouter(t)
// 1. Magic
magicW := postJSON(t, r, "/auth/magic", service_auth.MagicData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Email: "flow@example.com",
})
require.Equal(t, http.StatusOK, magicW.Code)
var magicResp map[string]any
require.NoError(t, json.Unmarshal(magicW.Body.Bytes(), &magicResp))
dataMap := magicResp["data"].(map[string]any)
rawURI := dataMap["uri"].(string)
code := extractQueryParam(t, rawURI, "code")
require.NotEmpty(t, code)
// 2. Redirect → 302
redirectW := getRequest(t, r, "/auth/redirect?client_id="+testutil.TestClientID+
"&redirect_uri=http://localhost/callback&code="+code+"&state=s")
require.Equal(t, http.StatusFound, redirectW.Code)
location := redirectW.Header().Get("Location")
require.NotEmpty(t, location)
tokenCode := extractQueryParam(t, location, "code")
require.NotEmpty(t, tokenCode)
// 3. Token
tokenW := postJSON(t, r, "/auth/token", service_auth.TokenData{Code: tokenCode})
require.Equal(t, http.StatusOK, tokenW.Code)
var tokenResp map[string]any
require.NoError(t, json.Unmarshal(tokenW.Body.Bytes(), &tokenResp))
tokenData := tokenResp["data"].(map[string]any)
accessToken := tokenData["access_token"].(string)
refreshToken := tokenData["refresh_token"].(string)
// 4. Refresh
refreshW := postJSON(t, r, "/auth/refresh", service_auth.RefreshData{
RefreshToken: refreshToken,
})
require.Equal(t, http.StatusOK, refreshW.Code)
var refreshResp map[string]any
require.NoError(t, json.Unmarshal(refreshW.Body.Bytes(), &refreshResp))
refreshData := refreshResp["data"].(map[string]any)
assert.NotEmpty(t, refreshData["access_token"])
assert.NotEqual(t, refreshToken, refreshData["refresh_token"])
_ = accessToken
}
// ---- Exchange ----
func TestExchangeHandlerNoAuth(t *testing.T) {
r := setupAuthRouter(t)
w := postJSON(t, r, "/auth/exchange", service_auth.ExchangeData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
})
// No auth header → 401
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
func TestExchangeHandlerSuccess(t *testing.T) {
r := setupAuthRouter(t)
// Step 1: Magic (debug mode, returns redirect URI with code)
magicW := postJSON(t, r, "/auth/magic", service_auth.MagicData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Email: "exchange@example.com",
})
require.Equal(t, http.StatusOK, magicW.Code)
var magicResp map[string]any
require.NoError(t, json.Unmarshal(magicW.Body.Bytes(), &magicResp))
rawURI := magicResp["data"].(map[string]any)["uri"].(string)
code := extractQueryParam(t, rawURI, "code")
// Step 2: Redirect → produces token code
redirectW := getRequest(t, r, "/auth/redirect?client_id="+testutil.TestClientID+
"&redirect_uri=http://localhost/callback&code="+code+"&state=s")
require.Equal(t, http.StatusFound, redirectW.Code)
location := redirectW.Header().Get("Location")
tokenCode := extractQueryParam(t, location, "code")
// Step 3: Token → get access token
tokenW := postJSON(t, r, "/auth/token", service_auth.TokenData{Code: tokenCode})
require.Equal(t, http.StatusOK, tokenW.Code)
var tokenResp map[string]any
require.NoError(t, json.Unmarshal(tokenW.Body.Bytes(), &tokenResp))
accessToken := tokenResp["data"].(map[string]any)["access_token"].(string)
// Step 4: Exchange (requires JWT Bearer token)
w := postWithBearer(t, r, "/auth/exchange", accessToken, service_auth.ExchangeData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
})
assert.Equal(t, http.StatusOK, w.Code)
var exchangeResp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &exchangeResp))
exchangeData, ok := exchangeResp["data"].(map[string]any)
require.True(t, ok)
assert.NotEmpty(t, exchangeData["redirect_uri"])
}