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: "technician", }) assert.NoError(t, err) assert.NotNil(t, dto) assert.Equal(t, "users_create_ok", dto.Username) assert.Equal(t, "technician", 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_EmployeeWithoutPasswordGeneratesSerial(t *testing.T) { svc := UsersService{} dto, err := svc.Create(models.CreateUserDTO{ Name: "普通员工", Phone: "13800000002", EmployeeNo: "employee_no_pwd", Position: "生产员工", Role: "employee", }) assert.NoError(t, err) assert.NotNil(t, dto) assert.Equal(t, "employee", dto.Role) assert.Len(t, dto.EmployeeSerials, 1) var user models.User assert.NoError(t, database.DB.Where("employee_no = ?", "employee_no_pwd").First(&user).Error) assert.Empty(t, user.Password) serialService := EmployeeSerialsService{} serial, err := serialService.Query(dto.EmployeeSerials[0].SerialNumber) assert.NoError(t, err) assert.NotNil(t, serial.Employee) assert.Equal(t, "普通员工", serial.Employee.Name) assert.Equal(t, "13800000002", serial.Employee.Phone) assert.Equal(t, "employee_no_pwd", serial.Employee.EmployeeNo) assert.Equal(t, "生产员工", serial.Employee.Position) database.DB.Unscoped().Where("employee_id = ?", user.ID).Delete(&models.EmployeeSerial{}) database.DB.Unscoped().Delete(&user) } func TestUsersService_Create_BackendRoleRequiresPassword(t *testing.T) { svc := UsersService{} _, err := svc.Create(models.CreateUserDTO{ Name: "无密码技术员", Phone: "13800000003", EmployeeNo: "tech_no_pwd", Position: "技术员", Role: "technician", }) 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: "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", Phone: "13800000004", EmployeeNo: "users_create_dup_2", Position: "技术员", 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(¤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: "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: "employee"} 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: "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, "technician", "") assert.NoError(t, err) for _, u := range results { assert.Equal(t, "technician", u.Role) } }