164 lines
4.7 KiB
Go
164 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/glebarez/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
"oci-portal/internal/model"
|
|
)
|
|
|
|
func newTestAuth(t *testing.T) *AuthService {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("open in-memory sqlite: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(&model.User{}); err != nil {
|
|
t.Fatalf("auto migrate: %v", err)
|
|
}
|
|
return NewAuthService(db, "test-jwt-secret")
|
|
}
|
|
|
|
func TestEnsureAdminCreatesUser(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
|
|
t.Fatalf("EnsureAdmin: %v", err)
|
|
}
|
|
if _, _, err := auth.Login(context.Background(), "admin", "pass123", "127.0.0.1", ""); err != nil {
|
|
t.Errorf("Login after EnsureAdmin: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureAdminDoesNotResetPassword(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
if err := auth.EnsureAdmin("admin", "first"); err != nil {
|
|
t.Fatalf("EnsureAdmin: %v", err)
|
|
}
|
|
if err := auth.EnsureAdmin("admin", "second"); err != nil {
|
|
t.Fatalf("EnsureAdmin twice: %v", err)
|
|
}
|
|
if _, _, err := auth.Login(context.Background(), "admin", "first", "127.0.0.1", ""); err != nil {
|
|
t.Errorf("Login with original password: %v", err)
|
|
}
|
|
if _, _, err := auth.Login(context.Background(), "admin", "second", "127.0.0.1", ""); !errors.Is(err, ErrInvalidCredentials) {
|
|
t.Errorf("Login with new password: got %v, want ErrInvalidCredentials", err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureAdminNoPasswordNoUsers(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
err := auth.EnsureAdmin("admin", "")
|
|
if err == nil {
|
|
t.Fatal("EnsureAdmin with no password and no users: got nil error, want failure")
|
|
}
|
|
if !strings.Contains(err.Error(), "ADMIN_PASSWORD") {
|
|
t.Errorf("error = %q, want mention of ADMIN_PASSWORD", err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureAdminNoPasswordWithExistingUser(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
if err := auth.EnsureAdmin("admin", "pass"); err != nil {
|
|
t.Fatalf("EnsureAdmin: %v", err)
|
|
}
|
|
if err := auth.EnsureAdmin("admin", ""); err != nil {
|
|
t.Errorf("EnsureAdmin without password but user exists: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoginFailures(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
|
|
t.Fatalf("EnsureAdmin: %v", err)
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
username string
|
|
password string
|
|
}{
|
|
{name: "密码错误", username: "admin", password: "wrong"},
|
|
{name: "用户不存在", username: "ghost", password: "pass123"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, _, err := auth.Login(context.Background(), tt.username, tt.password, "127.0.0.1", "")
|
|
if !errors.Is(err, ErrInvalidCredentials) {
|
|
t.Errorf("Login(%q, %q) error = %v, want ErrInvalidCredentials", tt.username, tt.password, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTokenRoundTrip(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
|
|
t.Fatalf("EnsureAdmin: %v", err)
|
|
}
|
|
token, expires, err := auth.Login(context.Background(), "admin", "pass123", "127.0.0.1", "")
|
|
if err != nil {
|
|
t.Fatalf("Login: %v", err)
|
|
}
|
|
if expires.IsZero() {
|
|
t.Error("expires is zero, want future time")
|
|
}
|
|
username, err := auth.ParseToken(token)
|
|
if err != nil {
|
|
t.Fatalf("ParseToken: %v", err)
|
|
}
|
|
if got, want := username, "admin"; got != want {
|
|
t.Errorf("username = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParseTokenRejectsForged(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
other := newTestAuth(t)
|
|
other.jwtSecret = []byte("different-secret")
|
|
if err := other.EnsureAdmin("admin", "pass"); err != nil {
|
|
t.Fatalf("EnsureAdmin: %v", err)
|
|
}
|
|
forged, _, err := other.Login(context.Background(), "admin", "pass", "127.0.0.1", "")
|
|
if err != nil {
|
|
t.Fatalf("Login: %v", err)
|
|
}
|
|
if _, err := auth.ParseToken(forged); err == nil {
|
|
t.Error("ParseToken(forged): got nil error, want failure")
|
|
}
|
|
if _, err := auth.ParseToken("not.a.token"); err == nil {
|
|
t.Error("ParseToken(garbage): got nil error, want failure")
|
|
}
|
|
}
|
|
|
|
func TestLogoutRevokesToken(t *testing.T) {
|
|
auth := newTestAuth(t)
|
|
token, _, err := auth.signToken("admin")
|
|
if err != nil {
|
|
t.Fatalf("signToken: %v", err)
|
|
}
|
|
if _, err := auth.ParseToken(token); err != nil {
|
|
t.Fatalf("ParseToken before logout: %v", err)
|
|
}
|
|
auth.Logout(token)
|
|
if _, err := auth.ParseToken(token); err == nil {
|
|
t.Error("ParseToken after logout: got nil error, want revoked")
|
|
}
|
|
// 幂等:重复登出与无效令牌登出都不应 panic,也不影响其他令牌
|
|
auth.Logout(token)
|
|
auth.Logout("not.a.token")
|
|
fresh, _, err := auth.signToken("admin")
|
|
if err != nil {
|
|
t.Fatalf("signToken fresh: %v", err)
|
|
}
|
|
if _, err := auth.ParseToken(fresh); err != nil {
|
|
t.Errorf("ParseToken(fresh) after revoking old: %v", err)
|
|
}
|
|
}
|