88 lines
3.3 KiB
Go
88 lines
3.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/user-management-system/internal/auth"
|
|
"github.com/user-management-system/internal/cache"
|
|
"github.com/user-management-system/internal/domain"
|
|
"github.com/user-management-system/internal/repository"
|
|
gormsqlite "gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
type failingL2Cache struct {
|
|
setErr error
|
|
}
|
|
|
|
func (f *failingL2Cache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
|
|
return f.setErr
|
|
}
|
|
func (f *failingL2Cache) Get(ctx context.Context, key string) (interface{}, error) { return nil, nil }
|
|
func (f *failingL2Cache) Delete(ctx context.Context, key string) error { return nil }
|
|
func (f *failingL2Cache) Exists(ctx context.Context, key string) (bool, error) { return false, nil }
|
|
func (f *failingL2Cache) Clear(ctx context.Context) error { return nil }
|
|
func (f *failingL2Cache) Increment(ctx context.Context, key string, delta int64, ttl time.Duration) (int64, error) {
|
|
return 0, nil
|
|
}
|
|
func (f *failingL2Cache) Close() error { return nil }
|
|
|
|
func TestAuthService_Logout_FailsClosedWhenBlacklistWriteFails(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:logoutfailclosed_%d?mode=memory&cache=shared", time.Now().UnixNano())
|
|
db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{DriverName: "sqlite", DSN: dsn}), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("open db failed: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(&domain.User{}, &domain.Role{}, &domain.UserRole{}, &domain.LoginLog{}, &domain.PasswordHistory{}); err != nil {
|
|
t.Fatalf("migrate failed: %v", err)
|
|
}
|
|
for _, role := range domain.PredefinedRoles {
|
|
roleCopy := role
|
|
if err := db.Create(&roleCopy).Error; err != nil {
|
|
t.Fatalf("seed role %s failed: %v", role.Code, err)
|
|
}
|
|
}
|
|
|
|
jwtManager, err := auth.NewJWTWithOptions(auth.JWTOptions{
|
|
HS256Secret: fmt.Sprintf("logout-failclosed-secret-%d", time.Now().UnixNano()),
|
|
AccessTokenExpire: 15 * time.Minute,
|
|
RefreshTokenExpire: 7 * 24 * time.Hour,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create jwt manager failed: %v", err)
|
|
}
|
|
|
|
userRepo := repository.NewUserRepository(db)
|
|
userRoleRepo := repository.NewUserRoleRepository(db)
|
|
roleRepo := repository.NewRoleRepository(db)
|
|
cacheManager := cache.NewCacheManager(cache.NewL1Cache(), &failingL2Cache{setErr: errors.New("forced blacklist failure")})
|
|
|
|
authSvc := NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute)
|
|
authSvc.SetRoleRepositories(userRoleRepo, roleRepo)
|
|
|
|
ctx := context.Background()
|
|
if _, err := authSvc.Register(ctx, &RegisterRequest{Username: "logoutfail", Password: "Password123!"}); err != nil {
|
|
t.Fatalf("register failed: %v", err)
|
|
}
|
|
loginResp, err := authSvc.Login(ctx, &LoginRequest{Username: "logoutfail", Password: "Password123!"}, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("login failed: %v", err)
|
|
}
|
|
|
|
err = authSvc.Logout(ctx, "logoutfail", &LogoutRequest{AccessToken: loginResp.AccessToken, RefreshToken: loginResp.RefreshToken})
|
|
if err == nil {
|
|
t.Fatal("expected logout to fail closed when blacklist write fails")
|
|
}
|
|
if !strings.Contains(err.Error(), "forced blacklist failure") {
|
|
t.Fatalf("expected propagated blacklist error, got: %v", err)
|
|
}
|
|
}
|