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{ Password: "password123", Name: "新技术员", Email: "new@example.com", Phone: "13800000001", EmployeeNo: "users_create_ok", Position: "软件工程师", Role: "software_engineer", }) assert.NoError(t, err) assert.NotNil(t, dto) assert.Equal(t, "users_create_ok", dto.Username) assert.Equal(t, "software_engineer", dto.Role) assert.Equal(t, "13800000001", dto.Phone) assert.Equal(t, "users_create_ok", dto.EmployeeNo) assert.Len(t, dto.EmployeeSerials, 1) database.DB.Unscoped().Where("username = ?", "users_create_ok").Delete(&models.User{}) database.DB.Unscoped().Where("employee_name = ?", "新技术员").Delete(&models.EmployeeSerial{}) } func TestUsersService_Create_BlocksEmployeeRole(t *testing.T) { svc := UsersService{} _, err := svc.Create(models.CreateUserDTO{ Name: "普通员工", Phone: "13800000002", EmployeeNo: "employee_no_pwd", Position: "生产员工", Role: "employee", }) assert.Error(t, err) assert.Contains(t, err.Error(), "权限管理只能创建") } func TestUsersService_Create_BackendRoleRequiresPassword(t *testing.T) { svc := UsersService{} _, err := svc.Create(models.CreateUserDTO{ Name: "无密码技术员", Phone: "13800000003", EmployeeNo: "tech_no_pwd", Position: "软件工程师", Role: "software_engineer", }) assert.Error(t, err) assert.Contains(t, err.Error(), "必须设置") } func TestUsersService_Create_DuplicateUsername(t *testing.T) { user := models.User{ Username: "users_create_dup", Password: "x", Name: "existing", Role: "software_engineer", } 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", Phone: "13800000004", EmployeeNo: "users_create_dup_2", Position: "软件工程师", Role: "software_engineer", }) 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: "software_engineer", }, 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: "software_engineer", } database.DB.Create(¤tAdmin) database.DB.Create(&target) defer database.DB.Unscoped().Delete(¤tAdmin) defer database.DB.Unscoped().Delete(&target) svc := UsersService{} updated, err := svc.Update(target.ID, models.UpdateUserDTO{ Name: "新名字", Role: "project_manager", }, currentAdmin.ID) assert.NoError(t, err) assert.Equal(t, "新名字", updated.Name) assert.Equal(t, "project_manager", 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: "software_engineer", } 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_ReturnsWorkOrderRoles(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"} software := models.User{Username: "assignable_software", Password: "x", Name: "S", Role: "software_engineer"} plain := models.User{Username: "assignable_user", Password: "x", Name: "U", Role: "employee"} database.DB.Create(&a) database.DB.Create(&tech) database.DB.Create(&software) database.DB.Create(&plain) defer database.DB.Unscoped().Delete(&a) defer database.DB.Unscoped().Delete(&tech) defer database.DB.Unscoped().Delete(&software) 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, "technician", usernames["assignable_tech"]) assert.Equal(t, "software_engineer", usernames["assignable_software"]) _, hasAdmin := usernames["assignable_admin"] assert.False(t, hasAdmin, "admin should not be assignable") _, 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: "software_engineer"} tech2 := models.User{Username: "findall_tech2", Password: "x", Name: "T2", Role: "software_engineer"} user1 := models.User{Username: "findall_user1", Password: "x", Name: "U1", Role: "employee"} 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, "software_engineer", "") assert.NoError(t, err) for _, u := range results { assert.Equal(t, "software_engineer", u.Role) } }