Files
backend-go/services/users_service_test.go
T
Frudrax Cheng 128bb7cda6 Add aftersales stats to dashboard and service-layer tests
- CompanyStatsOverviewDTO and GetStats() now include aftersales counts
  (total, pending confirmation, closed, rejected) and a recentAftersales list
- aftersales_service_test.go covers YYMMNN sequence, owner-only submit,
  state machine, phone last-4 check, reject increment, force-close
- users_service_test.go covers duplicate username, self-demotion guard,
  last-admin guard, password reset, assignable filter

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 11:04:23 +08:00

207 lines
6.0 KiB
Go

package services
import (
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/bcrypt"
"git.beifan.cn/trace-system/backend-go/database"
"git.beifan.cn/trace-system/backend-go/models"
)
func TestUsersService_Create_Success(t *testing.T) {
svc := UsersService{}
dto, err := svc.Create(models.CreateUserDTO{
Username: "users_create_ok",
Password: "password123",
Name: "新技术员",
Email: "new@example.com",
Role: "technician",
})
assert.NoError(t, err)
assert.NotNil(t, dto)
assert.Equal(t, "users_create_ok", dto.Username)
assert.Equal(t, "technician", dto.Role)
database.DB.Unscoped().Where("username = ?", "users_create_ok").Delete(&models.User{})
}
func TestUsersService_Create_DuplicateUsername(t *testing.T) {
user := models.User{
Username: "users_create_dup",
Password: "x",
Name: "existing",
Role: "technician",
}
database.DB.Create(&user)
defer database.DB.Unscoped().Delete(&user)
svc := UsersService{}
_, err := svc.Create(models.CreateUserDTO{
Username: "users_create_dup",
Password: "password123",
Name: "duplicate",
Role: "technician",
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "用户名已存在")
}
func TestUsersService_Update_BlocksSelfDemotion(t *testing.T) {
admin := models.User{
Username: "users_self_admin",
Password: "x",
Name: "self admin",
Role: "admin",
}
database.DB.Create(&admin)
defer database.DB.Unscoped().Delete(&admin)
svc := UsersService{}
_, err := svc.Update(admin.ID, models.UpdateUserDTO{
Role: "technician",
}, admin.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "不能修改自己的管理员角色")
}
func TestUsersService_Update_AllowsRoleChangeForOthers(t *testing.T) {
currentAdmin := models.User{
Username: "users_update_admin",
Password: "x",
Name: "current",
Role: "admin",
}
target := models.User{
Username: "users_update_target",
Password: "x",
Name: "target",
Role: "technician",
}
database.DB.Create(&currentAdmin)
database.DB.Create(&target)
defer database.DB.Unscoped().Delete(&currentAdmin)
defer database.DB.Unscoped().Delete(&target)
svc := UsersService{}
updated, err := svc.Update(target.ID, models.UpdateUserDTO{
Name: "新名字",
Role: "admin",
}, currentAdmin.ID)
assert.NoError(t, err)
assert.Equal(t, "新名字", updated.Name)
assert.Equal(t, "admin", updated.Role)
}
func TestUsersService_ResetPassword_ChangesHash(t *testing.T) {
hashed, _ := bcrypt.GenerateFromPassword([]byte("oldpass"), bcrypt.DefaultCost)
user := models.User{
Username: "users_reset_pwd",
Password: string(hashed),
Name: "reset",
Role: "technician",
}
database.DB.Create(&user)
defer database.DB.Unscoped().Delete(&user)
svc := UsersService{}
err := svc.ResetPassword(user.ID, "newpass456")
assert.NoError(t, err)
var refreshed models.User
database.DB.First(&refreshed, user.ID)
assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(refreshed.Password), []byte("newpass456")))
assert.Error(t, bcrypt.CompareHashAndPassword([]byte(refreshed.Password), []byte("oldpass")))
}
func TestUsersService_Delete_BlocksSelf(t *testing.T) {
user := models.User{
Username: "users_delete_self",
Password: "x",
Name: "self",
Role: "admin",
}
database.DB.Create(&user)
defer database.DB.Unscoped().Delete(&user)
svc := UsersService{}
err := svc.Delete(user.ID, user.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "不能删除自己")
}
func TestUsersService_Delete_BlocksLastAdmin(t *testing.T) {
// 清理可能存在的其他 admin(来自 seed 或前面测试)
database.DB.Unscoped().Where("role = ?", "admin").Delete(&models.User{})
last := models.User{
Username: "users_last_admin",
Password: "x",
Name: "last",
Role: "admin",
}
other := models.User{
Username: "users_other_admin_actor",
Password: "x",
Name: "actor",
Role: "admin",
}
database.DB.Create(&last)
database.DB.Create(&other)
svc := UsersService{}
// 当前调用者是 other(不是 last),但删除会让 last 变成最后一个 admin
// 删 last 时计数会变 0,所以拒绝
database.DB.Unscoped().Delete(&other)
err := svc.Delete(last.ID, 999999)
assert.Error(t, err)
assert.Contains(t, err.Error(), "不能删除最后一个管理员")
database.DB.Unscoped().Delete(&last)
}
func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
a := models.User{Username: "assignable_admin", Password: "x", Name: "A", Role: "admin"}
tech := models.User{Username: "assignable_tech", Password: "x", Name: "T", Role: "technician"}
plain := models.User{Username: "assignable_user", Password: "x", Name: "U", Role: "user"}
database.DB.Create(&a)
database.DB.Create(&tech)
database.DB.Create(&plain)
defer database.DB.Unscoped().Delete(&a)
defer database.DB.Unscoped().Delete(&tech)
defer database.DB.Unscoped().Delete(&plain)
svc := UsersService{}
users, err := svc.FindAssignable()
assert.NoError(t, err)
usernames := make(map[string]string)
for _, u := range users {
usernames[u.Username] = u.Role
}
assert.Equal(t, "admin", usernames["assignable_admin"])
assert.Equal(t, "technician", usernames["assignable_tech"])
_, hasPlain := usernames["assignable_user"]
assert.False(t, hasPlain, "plain user should not be assignable")
}
func TestUsersService_FindAll_FilterByRole(t *testing.T) {
tech1 := models.User{Username: "findall_tech1", Password: "x", Name: "T1", Role: "technician"}
tech2 := models.User{Username: "findall_tech2", Password: "x", Name: "T2", Role: "technician"}
user1 := models.User{Username: "findall_user1", Password: "x", Name: "U1", Role: "user"}
database.DB.Create(&tech1)
database.DB.Create(&tech2)
database.DB.Create(&user1)
defer database.DB.Unscoped().Delete(&tech1)
defer database.DB.Unscoped().Delete(&tech2)
defer database.DB.Unscoped().Delete(&user1)
svc := UsersService{}
results, _, _, err := svc.FindAll(1, 50, "technician", "")
assert.NoError(t, err)
for _, u := range results {
assert.Equal(t, "technician", u.Role)
}
}