Files
cms-server/service/service_auth/auth_test.go
Asai Neko 210b8b08ce
All checks were successful
Server Check Build (NixCN CMS) TeamCity build finished
Add test for all components
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-03-26 18:19:26 +08:00

329 lines
7.5 KiB
Go

package service_auth
import (
"context"
"net/url"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"nixcn-cms/data"
"nixcn-cms/internal/exception"
"nixcn-cms/testutil"
)
func newSvc() AuthService { return NewAuthService() }
func parseURI(t *testing.T, raw string) *url.URL {
t.Helper()
u, err := url.Parse(raw)
require.NoError(t, err)
return u
}
type tokenPair struct {
AccessToken string
RefreshToken string
}
// getTokensForUser runs the full magic → redirect → token flow and
// returns the issued token pair.
func getTokensForUser(t *testing.T, ctx context.Context, email string) tokenPair {
t.Helper()
svc := newSvc()
magicResult := svc.Magic(&MagicPayload{
Context: ctx,
Data: &MagicData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Email: email,
},
})
require.Equal(t, 200, magicResult.Common.HttpCode)
magicURI := parseURI(t, magicResult.Data.Uri)
redirectResult := svc.Redirect(&RedirectPayload{
Context: ctx,
Data: &RedirectData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Code: magicURI.Query().Get("code"),
},
})
require.Equal(t, 200, redirectResult.Common.HttpCode)
finalURI := parseURI(t, redirectResult.Data)
tokenResult := svc.Token(&TokenPayload{
Context: ctx,
Data: &TokenData{Code: finalURI.Query().Get("code")},
})
require.Equal(t, 200, tokenResult.Common.HttpCode)
return tokenPair{
AccessToken: tokenResult.Data.AccessToken,
RefreshToken: tokenResult.Data.RefreshToken,
}
}
// ---- Exchange ----
func TestExchangeSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
u := testutil.SeedUser(t, "exchange@example.com", 10)
testutil.SeedClient(t)
svc := newSvc()
result := svc.Exchange(&ExchangePayload{
Context: ctx,
UserId: u.UserId,
Data: &ExchangeData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "state123",
},
})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.Contains(t, result.Data.RedirectUri, "code=")
}
func TestExchangeInvalidRedirectUri(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
u := testutil.SeedUser(t, "exchange2@example.com", 10)
testutil.SeedClient(t)
svc := newSvc()
result := svc.Exchange(&ExchangePayload{
Context: ctx,
UserId: u.UserId,
Data: &ExchangeData{
ClientId: testutil.TestClientID,
RedirectUri: "://invalid-url",
State: "s",
},
})
assert.Equal(t, 400, result.Common.HttpCode)
}
func TestExchangeUserNotFound(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
svc := newSvc()
result := svc.Exchange(&ExchangePayload{
Context: ctx,
UserId: uuid.New(),
Data: &ExchangeData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
},
})
assert.Equal(t, 500, result.Common.HttpCode)
}
// ---- Magic (debug_mode = true, skips Turnstile and Email) ----
func TestMagicDebugMode(t *testing.T) {
testutil.Setup(t) // sets server.debug_mode = true
ctx := context.Background()
svc := newSvc()
result := svc.Magic(&MagicPayload{
Context: ctx,
Data: &MagicData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "state",
Email: "magic@example.com",
},
})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.Contains(t, result.Data.Uri, "/app/api/v1/auth/redirect")
}
// ---- Redirect ----
func TestRedirectCreatesNewUser(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
testutil.SeedClient(t)
svc := newSvc()
magicResult := svc.Magic(&MagicPayload{
Context: ctx,
Data: &MagicData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Email: "newuser@example.com",
},
})
require.Equal(t, 200, magicResult.Common.HttpCode)
require.NotNil(t, magicResult.Data)
u := parseURI(t, magicResult.Data.Uri)
code := u.Query().Get("code")
require.NotEmpty(t, code)
result := svc.Redirect(&RedirectPayload{
Context: ctx,
Data: &RedirectData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Code: code,
},
})
assert.Equal(t, 200, result.Common.HttpCode)
assert.Contains(t, result.Data, "code=")
email := "newuser@example.com"
user, err := new(data.User).GetByEmail(ctx, &email)
require.NoError(t, err)
assert.Equal(t, "newuser@example.com", user.Email)
}
func TestRedirectInvalidCode(t *testing.T) {
testutil.Setup(t)
svc := newSvc()
result := svc.Redirect(&RedirectPayload{
Context: context.Background(),
Data: &RedirectData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Code: "bad-code",
},
})
assert.Equal(t, 403, result.Common.HttpCode)
}
func TestRedirectClientNotFound(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
svc := newSvc()
magicResult := svc.Magic(&MagicPayload{
Context: ctx,
Data: &MagicData{
ClientId: "no-such-client",
RedirectUri: "http://localhost/callback",
State: "s",
Email: "x@example.com",
},
})
require.Equal(t, 200, magicResult.Common.HttpCode)
u := parseURI(t, magicResult.Data.Uri)
code := u.Query().Get("code")
result := svc.Redirect(&RedirectPayload{
Context: ctx,
Data: &RedirectData{
ClientId: "no-such-client",
RedirectUri: "http://localhost/callback",
State: "s",
Code: code,
},
})
assert.Equal(t, 400, result.Common.HttpCode)
assert.Equal(t, exception.AuthRedirectClientNotFound, result.Common.Exception.Original)
}
// ---- Token ----
func TestTokenSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
testutil.SeedClient(t)
tokens := getTokensForUser(t, ctx, "token@example.com")
assert.NotEmpty(t, tokens.AccessToken)
assert.NotEmpty(t, tokens.RefreshToken)
}
func TestTokenInvalidCode(t *testing.T) {
testutil.Setup(t)
svc := newSvc()
result := svc.Token(&TokenPayload{
Context: context.Background(),
Data: &TokenData{Code: "invalid"},
})
assert.Equal(t, 403, result.Common.HttpCode)
}
// ---- Refresh ----
func TestRefreshSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
testutil.SeedClient(t)
tokens := getTokensForUser(t, ctx, "refresh@example.com")
svc := newSvc()
result := svc.Refresh(&RefreshPayload{
Context: ctx,
Data: &RefreshData{RefreshToken: tokens.RefreshToken},
})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.NotEmpty(t, result.Data.AccessToken)
assert.NotEmpty(t, result.Data.RefreshToken)
assert.NotEqual(t, tokens.RefreshToken, result.Data.RefreshToken)
}
func TestRefreshInvalidToken(t *testing.T) {
testutil.Setup(t)
svc := newSvc()
result := svc.Refresh(&RefreshPayload{
Context: context.Background(),
Data: &RefreshData{RefreshToken: "bad-refresh"},
})
assert.Equal(t, 401, result.Common.HttpCode)
}
func TestRefreshTokenExpires(t *testing.T) {
mr := testutil.Setup(t)
ctx := context.Background()
testutil.SeedClient(t)
tokens := getTokensForUser(t, ctx, "expire@example.com")
mr.FastForward(7*24*time.Hour + time.Minute)
svc := newSvc()
result := svc.Refresh(&RefreshPayload{
Context: ctx,
Data: &RefreshData{RefreshToken: tokens.RefreshToken},
})
assert.Equal(t, 401, result.Common.HttpCode)
}