fix: harden auth flows and align api contracts
This commit is contained in:
167
internal/service/auth_cache_invalidator_test.go
Normal file
167
internal/service/auth_cache_invalidator_test.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/user-management-system/internal/cache"
|
||||
"github.com/user-management-system/internal/domain"
|
||||
"github.com/user-management-system/internal/repository"
|
||||
"github.com/user-management-system/internal/service"
|
||||
gormsqlite "gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type cacheInvalidatorHarness struct {
|
||||
l1 *cache.L1Cache
|
||||
}
|
||||
|
||||
func (h *cacheInvalidatorHarness) InvalidateUserState(userID int64) {
|
||||
h.l1.Delete("user_state:" + strconv.FormatInt(userID, 10))
|
||||
}
|
||||
|
||||
func (h *cacheInvalidatorHarness) InvalidateUserPerms(userID int64) {
|
||||
h.l1.Delete("user_perms:" + strconv.FormatInt(userID, 10))
|
||||
}
|
||||
|
||||
func setupCacheInvalidationDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
|
||||
DriverName: "sqlite",
|
||||
DSN: "file:cache_invalidation?mode=memory&cache=shared",
|
||||
}), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite failed: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&domain.User{}, &domain.Role{}, &domain.UserRole{}, &domain.Permission{}, &domain.RolePermission{}); err != nil {
|
||||
t.Fatalf("migrate failed: %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func TestUserService_InvalidateStateCacheOnStatusChange(t *testing.T) {
|
||||
db := setupCacheInvalidationDB(t)
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
userRoleRepo := repository.NewUserRoleRepository(db)
|
||||
roleRepo := repository.NewRoleRepository(db)
|
||||
userSvc := service.NewUserService(userRepo, userRoleRepo, roleRepo, nil)
|
||||
l1 := cache.NewL1Cache()
|
||||
userSvc.SetAuthCacheInvalidator(&cacheInvalidatorHarness{l1: l1})
|
||||
|
||||
user := &domain.User{Username: "statecache", Password: "x", Status: domain.UserStatusActive}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user failed: %v", err)
|
||||
}
|
||||
l1.Set("user_state:"+strconv.FormatInt(user.ID, 10), "cached", time.Minute)
|
||||
|
||||
if err := userSvc.UpdateStatus(context.Background(), user.ID, domain.UserStatusInactive); err != nil {
|
||||
t.Fatalf("UpdateStatus failed: %v", err)
|
||||
}
|
||||
if _, ok := l1.Get("user_state:" + strconv.FormatInt(user.ID, 10)); ok {
|
||||
t.Fatal("expected user_state cache to be invalidated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_InvalidatePermCacheOnAssignRoles(t *testing.T) {
|
||||
db := setupCacheInvalidationDB(t)
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
userRoleRepo := repository.NewUserRoleRepository(db)
|
||||
roleRepo := repository.NewRoleRepository(db)
|
||||
userSvc := service.NewUserService(userRepo, userRoleRepo, roleRepo, nil)
|
||||
l1 := cache.NewL1Cache()
|
||||
userSvc.SetAuthCacheInvalidator(&cacheInvalidatorHarness{l1: l1})
|
||||
|
||||
user := &domain.User{Username: "permcache", Password: "x", Status: domain.UserStatusActive}
|
||||
role := &domain.Role{Name: "role1", Code: "role1", Status: domain.RoleStatusEnabled}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user failed: %v", err)
|
||||
}
|
||||
if err := db.Create(role).Error; err != nil {
|
||||
t.Fatalf("create role failed: %v", err)
|
||||
}
|
||||
l1.Set("user_perms:"+strconv.FormatInt(user.ID, 10), "cached", time.Minute)
|
||||
|
||||
if err := userSvc.AssignRoles(context.Background(), user.ID, []int64{role.ID}); err != nil {
|
||||
t.Fatalf("AssignRoles failed: %v", err)
|
||||
}
|
||||
if _, ok := l1.Get("user_perms:" + strconv.FormatInt(user.ID, 10)); ok {
|
||||
t.Fatal("expected user_perms cache to be invalidated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleService_InvalidatePermCacheOnAssignPermissions(t *testing.T) {
|
||||
db := setupCacheInvalidationDB(t)
|
||||
roleRepo := repository.NewRoleRepository(db)
|
||||
rolePermRepo := repository.NewRolePermissionRepository(db)
|
||||
userRoleRepo := repository.NewUserRoleRepository(db)
|
||||
roleSvc := service.NewRoleService(roleRepo, rolePermRepo)
|
||||
roleSvc.SetUserRoleRepository(userRoleRepo)
|
||||
l1 := cache.NewL1Cache()
|
||||
roleSvc.SetAuthCacheInvalidator(&cacheInvalidatorHarness{l1: l1})
|
||||
|
||||
user := &domain.User{Username: "rolepermcache", Password: "x", Status: domain.UserStatusActive}
|
||||
role := &domain.Role{Name: "role2", Code: "role2", Status: domain.RoleStatusEnabled}
|
||||
perm := &domain.Permission{Name: "perm1", Code: "perm1", Type: domain.PermissionTypeMenu, Status: domain.PermissionStatusEnabled}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user failed: %v", err)
|
||||
}
|
||||
if err := db.Create(role).Error; err != nil {
|
||||
t.Fatalf("create role failed: %v", err)
|
||||
}
|
||||
if err := db.Create(perm).Error; err != nil {
|
||||
t.Fatalf("create permission failed: %v", err)
|
||||
}
|
||||
if err := db.Create(&domain.UserRole{UserID: user.ID, RoleID: role.ID}).Error; err != nil {
|
||||
t.Fatalf("create user role failed: %v", err)
|
||||
}
|
||||
l1.Set("user_perms:"+strconv.FormatInt(user.ID, 10), "cached", time.Minute)
|
||||
|
||||
if err := roleSvc.AssignPermissions(context.Background(), role.ID, []int64{perm.ID}); err != nil {
|
||||
t.Fatalf("AssignPermissions failed: %v", err)
|
||||
}
|
||||
if _, ok := l1.Get("user_perms:" + strconv.FormatInt(user.ID, 10)); ok {
|
||||
t.Fatal("expected user_perms cache to be invalidated after role permission change")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermissionService_InvalidatePermCacheOnStatusChange(t *testing.T) {
|
||||
db := setupCacheInvalidationDB(t)
|
||||
permRepo := repository.NewPermissionRepository(db)
|
||||
rolePermRepo := repository.NewRolePermissionRepository(db)
|
||||
userRoleRepo := repository.NewUserRoleRepository(db)
|
||||
permSvc := service.NewPermissionService(permRepo)
|
||||
permSvc.SetRolePermissionRepository(rolePermRepo)
|
||||
permSvc.SetUserRoleRepository(userRoleRepo)
|
||||
l1 := cache.NewL1Cache()
|
||||
permSvc.SetAuthCacheInvalidator(&cacheInvalidatorHarness{l1: l1})
|
||||
|
||||
user := &domain.User{Username: "permstatuscache", Password: "x", Status: domain.UserStatusActive}
|
||||
role := &domain.Role{Name: "role3", Code: "role3", Status: domain.RoleStatusEnabled}
|
||||
perm := &domain.Permission{Name: "perm2", Code: "perm2", Type: domain.PermissionTypeMenu, Status: domain.PermissionStatusEnabled}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("create user failed: %v", err)
|
||||
}
|
||||
if err := db.Create(role).Error; err != nil {
|
||||
t.Fatalf("create role failed: %v", err)
|
||||
}
|
||||
if err := db.Create(perm).Error; err != nil {
|
||||
t.Fatalf("create permission failed: %v", err)
|
||||
}
|
||||
if err := db.Create(&domain.UserRole{UserID: user.ID, RoleID: role.ID}).Error; err != nil {
|
||||
t.Fatalf("create user role failed: %v", err)
|
||||
}
|
||||
if err := db.Create(&domain.RolePermission{RoleID: role.ID, PermissionID: perm.ID}).Error; err != nil {
|
||||
t.Fatalf("create role permission failed: %v", err)
|
||||
}
|
||||
l1.Set("user_perms:"+strconv.FormatInt(user.ID, 10), "cached", time.Minute)
|
||||
|
||||
if err := permSvc.UpdatePermissionStatus(context.Background(), perm.ID, domain.PermissionStatusDisabled); err != nil {
|
||||
t.Fatalf("UpdatePermissionStatus failed: %v", err)
|
||||
}
|
||||
if _, ok := l1.Get("user_perms:" + strconv.FormatInt(user.ID, 10)); ok {
|
||||
t.Fatal("expected user_perms cache to be invalidated after permission status change")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user