feat: bootstrap reusable GoOmni framework
This commit is contained in:
122
auth/auth_test.go
Normal file
122
auth/auth_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.yuntuo.hk/k2safe/go-omni"
|
||||
"git.yuntuo.hk/k2safe/go-omni/auth"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestRequireAuthAndRole(t *testing.T) {
|
||||
app := omni.New(omni.WithMode(gin.TestMode))
|
||||
authenticator := auth.StaticAuthenticator{"good-token": {UserID: "user-1", Roles: []string{"user"}}}
|
||||
private := app.Group("/private", auth.RequireAuth(authenticator))
|
||||
private.GET("/me", func(ctx *omni.Context) error {
|
||||
principal, ok := auth.FromContext(ctx)
|
||||
if !ok {
|
||||
t.Fatal("principal was not attached")
|
||||
}
|
||||
return ctx.OK(principal)
|
||||
})
|
||||
private.GET("/admin", func(ctx *omni.Context) error {
|
||||
return ctx.OK(map[string]string{"ok": "true"})
|
||||
}, auth.RequireRole("admin"))
|
||||
|
||||
tests := []struct {
|
||||
name, authorization, path, code string
|
||||
status int
|
||||
}{
|
||||
{"missing", "", "/private/me", "UNAUTHENTICATED", http.StatusUnauthorized},
|
||||
{"malformed", "Token good-token", "/private/me", "UNAUTHENTICATED", http.StatusUnauthorized},
|
||||
{"invalid", "Bearer bad-token", "/private/me", "UNAUTHENTICATED", http.StatusUnauthorized},
|
||||
{"valid", "Bearer good-token", "/private/me", "OK", http.StatusOK},
|
||||
{"forbidden", "Bearer good-token", "/private/admin", "FORBIDDEN", http.StatusForbidden},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, test.path, nil)
|
||||
if test.authorization != "" {
|
||||
request.Header.Set("Authorization", test.authorization)
|
||||
}
|
||||
app.ServeHTTP(recorder, request)
|
||||
if recorder.Code != test.status {
|
||||
t.Fatalf("expected status %d, got %d: %s", test.status, recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var body struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Code != test.code {
|
||||
t.Fatalf("expected code %q, got %q", test.code, body.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalAuth(t *testing.T) {
|
||||
app := omni.New(omni.WithMode(gin.TestMode))
|
||||
app.GET("/public", func(ctx *omni.Context) error {
|
||||
principal, authenticated := auth.FromContext(ctx)
|
||||
return ctx.OK(map[string]any{"authenticated": authenticated, "userId": principal.UserID})
|
||||
}, auth.OptionalAuth(auth.StaticAuthenticator{"token": {UserID: "user-1"}}))
|
||||
|
||||
for _, test := range []struct {
|
||||
name, token string
|
||||
wantAuth bool
|
||||
status int
|
||||
}{
|
||||
{"anonymous", "", false, http.StatusOK},
|
||||
{"authenticated", "Bearer token", true, http.StatusOK},
|
||||
{"invalid", "Bearer bad", false, http.StatusUnauthorized},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/public", nil)
|
||||
if test.token != "" {
|
||||
request.Header.Set("Authorization", test.token)
|
||||
}
|
||||
app.ServeHTTP(recorder, request)
|
||||
if recorder.Code != test.status {
|
||||
t.Fatalf("expected status %d, got %d", test.status, recorder.Code)
|
||||
}
|
||||
if test.wantAuth {
|
||||
var body struct {
|
||||
Data struct {
|
||||
Authenticated bool `json:"authenticated"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !body.Data.Authenticated {
|
||||
t.Fatal("expected authenticated response")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaticAuthenticatorDoesNotExposeMutableSlices(t *testing.T) {
|
||||
authenticator := auth.StaticAuthenticator{"token": {UserID: "user-1", Roles: []string{"admin"}, Claims: map[string]any{"tenant": "a"}}}
|
||||
principal, err := authenticator.Authenticate(context.Background(), "token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
principal.Roles[0] = "changed"
|
||||
principal.Claims["tenant"] = "changed"
|
||||
again, err := authenticator.Authenticate(context.Background(), "token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !again.HasRole("admin") || again.Claims["tenant"] != "a" {
|
||||
t.Fatalf("static authenticator returned mutable state: %+v", again)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user