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") } }