From 26c549a95a437df35f5b6562eec19ab80f57c025 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 10 Jun 2026 09:37:40 +0200 Subject: [PATCH] fix(client): match clients by email for delete/update, not credentials Delete/update located the client in an inbound's settings JSON by the record's credential (uuid/password/auth). When that credential drifted from the inbound JSON -- e.g. a rotated UUID left behind, or duplicated by a past partial-update bug -- the lookup failed with "Client Not Found In Inbound For ID: " and aborted the whole operation, making the client impossible to remove from the panel. Key every delete/update/detach path on email, the client's stable identity. This survives credential drift and heals duplicate-email entries by removing all of them. - Delete/DeleteByEmail/Detach/DetachByEmailMany -> DelInboundClientByEmail - delInboundClients / bulkDelInboundClients: match settings by email - UpdateInboundClient: locate the entry to replace by email (param clientId -> oldEmail); update all callers to pass the email - bulkAdjustInboundClients: match by email - writeBackClientSubID: pass email; drop unused sourceProtocol param - make per-inbound deletion idempotent via ErrClientNotInInbound - remove now-orphaned DelInboundClient, clientKeyForProtocol and getClientPrimaryKey; scale test deletes by email --- web/service/client.go | 432 ++++++------------------ web/service/inbound.go | 24 +- web/service/sync_scale_postgres_test.go | 6 +- 3 files changed, 103 insertions(+), 359 deletions(-) diff --git a/web/service/client.go b/web/service/client.go index f0579f359..cfba41241 100644 --- a/web/service/client.go +++ b/web/service/client.go @@ -63,24 +63,14 @@ func (c ClientWithAttachments) MarshalJSON() ([]byte, error) { return out, nil } -func clientKeyForProtocol(p model.Protocol, rec *model.ClientRecord) string { - if rec == nil { - return "" - } - switch p { - case model.Trojan: - return rec.Password - case model.Shadowsocks: - return rec.Email - case model.Hysteria: - return rec.Auth - default: - return rec.UUID - } -} - type ClientService struct{} +// ErrClientNotInInbound is returned (wrapped) when a client cannot be located +// in an inbound's settings during deletion. Deletion treats it as non-fatal so +// the operation stays idempotent and tolerant of pre-existing data drift +// between the clients table and the inbound's settings JSON. +var ErrClientNotInInbound = errors.New("client not found in inbound") + // Short-lived tombstone of just-deleted client emails so that a node snapshot // arriving between delete and node-side processing doesn't resurrect them. var ( @@ -836,8 +826,7 @@ func (s *ClientService) Update(inboundSvc *InboundService, id int, updated model } return needRestart, getErr } - oldKey := clientKeyForProtocol(inbound.Protocol, existing) - if oldKey == "" { + if existing.Email == "" { continue } if err := s.fillProtocolDefaults(&updated, inbound); err != nil { @@ -850,7 +839,7 @@ func (s *ClientService) Update(inboundSvc *InboundService, id int, updated model nr, upErr := s.UpdateInboundClient(inboundSvc, &model.Inbound{ Id: ibId, Settings: string(settingsPayload), - }, oldKey) + }, existing.Email) if upErr != nil { return needRestart, upErr } @@ -893,19 +882,27 @@ func (s *ClientService) Delete(inboundSvc *InboundService, id int, keepTraffic b needRestart := false for _, ibId := range inboundIds { - inbound, getErr := inboundSvc.GetInbound(ibId) - if getErr != nil { + if _, getErr := inboundSvc.GetInbound(ibId); getErr != nil { if errors.Is(getErr, gorm.ErrRecordNotFound) { continue } return needRestart, getErr } - key := clientKeyForProtocol(inbound.Protocol, existing) - if key == "" { + + // Always delete by email — the client's stable identity. This removes + // every matching entry from the inbound's settings even when the stored + // credential (UUID/password/auth) drifted from the inbound JSON, or a + // duplicate entry with the same email exists. + if existing.Email == "" { continue } - nr, delErr := s.DelInboundClient(inboundSvc, ibId, key, false) + nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, false) if delErr != nil { + // The client is already absent from this inbound (data drift or a + // retried delete). Skip it — deletion stays idempotent. + if errors.Is(delErr, ErrClientNotInInbound) { + continue + } return needRestart, delErr } if nr { @@ -1220,8 +1217,8 @@ func (s *ClientService) BulkDetach(inboundSvc *InboundService, emails []string, // delInboundClients removes several clients from a single inbound in one pass: // one settings rewrite, one runtime sweep, one Save and one SyncInbound for the // whole batch, instead of repeating the full per-client cycle. It mirrors the -// semantics of DelInboundClient for each removed client. needRestart is the OR -// across all removals. +// semantics of DelInboundClientByEmail for each removed client. needRestart is +// the OR across all removals. func (s *ClientService) delInboundClients(inboundSvc *InboundService, inboundId int, recs []*model.ClientRecord, keepTraffic bool) (bool, error) { if len(recs) == 0 { return false, nil @@ -1239,20 +1236,12 @@ func (s *ClientService) delInboundClients(inboundSvc *InboundService, inboundId return false, err } - clientKey := "id" - switch oldInbound.Protocol { - case "trojan": - clientKey = "password" - case "shadowsocks": - clientKey = "email" - case "hysteria": - clientKey = "auth" - } - + // Match by email — the client's stable identity (see Delete). Removes every + // entry carrying a wanted email, independent of credential drift. wanted := make(map[string]struct{}, len(recs)) for _, rec := range recs { - if k := clientKeyForProtocol(oldInbound.Protocol, rec); k != "" { - wanted[k] = struct{}{} + if rec.Email != "" { + wanted[rec.Email] = struct{}{} } } @@ -1273,9 +1262,8 @@ func (s *ClientService) delInboundClients(inboundSvc *InboundService, inboundId newClients = append(newClients, client) continue } - cid, _ := c[clientKey].(string) - if _, hit := wanted[cid]; hit && cid != "" { - email, _ := c["email"].(string) + email, _ := c["email"].(string) + if _, hit := wanted[email]; hit && email != "" { enable, _ := c["enable"].(bool) removed = append(removed, removedClient{email: email, needApiDel: enable}) continue @@ -1417,6 +1405,9 @@ func (s *ClientService) DeleteByEmail(inboundSvc *InboundService, email string, for _, ibId := range inboundIds { nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, email, false) if delErr != nil { + if errors.Is(delErr, ErrClientNotInInbound) { + continue + } return needRestart, delErr } if nr { @@ -2720,29 +2711,15 @@ func (s *ClientService) bulkAdjustInboundClients( return res } - clientKey := "id" - switch oldInbound.Protocol { - case model.Trojan: - clientKey = "password" - case model.Shadowsocks: - clientKey = "email" - case model.Hysteria: - clientKey = "auth" - } - - keyToEmail := make(map[string]string, len(emails)) + // Match by email — the client's stable identity (see Delete). Credentials + // can drift from the inbound JSON, so they are never used for matching. + wantedEmails := make(map[string]struct{}, len(emails)) for _, email := range emails { - entry := plan[email] - if entry == nil { + if plan[email] == nil { res.perEmailSkipped[email] = "client not found" continue } - key := clientKeyForProtocol(oldInbound.Protocol, entry.record) - if key == "" { - res.perEmailSkipped[email] = "missing client key for protocol" - continue - } - keyToEmail[key] = email + wantedEmails[email] = struct{}{} } interfaceClients, _ := settings["clients"].([]any) @@ -2753,9 +2730,8 @@ func (s *ClientService) bulkAdjustInboundClients( if !ok { continue } - cKey, _ := c[clientKey].(string) - targetEmail, found := keyToEmail[cKey] - if !found { + targetEmail, _ := c["email"].(string) + if _, want := wantedEmails[targetEmail]; !want || targetEmail == "" { continue } entry := plan[targetEmail] @@ -2770,7 +2746,7 @@ func (s *ClientService) bulkAdjustInboundClients( foundEmails[targetEmail] = true } - for _, email := range keyToEmail { + for email := range wantedEmails { if !foundEmails[email] { res.perEmailSkipped[email] = "Client Not Found In Inbound" } @@ -3031,29 +3007,15 @@ func (s *ClientService) bulkDelInboundClients( return res } - clientKey := "id" - switch oldInbound.Protocol { - case model.Trojan: - clientKey = "password" - case model.Shadowsocks: - clientKey = "email" - case model.Hysteria: - clientKey = "auth" - } - - keyToEmail := make(map[string]string, len(emails)) + // Match by email — the client's stable identity (see Delete). Removes every + // entry carrying a wanted email, independent of credential drift. + wantedEmails := make(map[string]struct{}, len(emails)) for _, email := range emails { - rec := records[email] - if rec == nil { + if records[email] == nil { res.perEmailSkipped[email] = "client not found" continue } - key := clientKeyForProtocol(oldInbound.Protocol, rec) - if key == "" { - res.perEmailSkipped[email] = "missing client key for protocol" - continue - } - keyToEmail[key] = email + wantedEmails[email] = struct{}{} } interfaceClients, _ := settings["clients"].([]any) @@ -3066,19 +3028,17 @@ func (s *ClientService) bulkDelInboundClients( newClients = append(newClients, client) continue } - cKey, _ := c[clientKey].(string) - if targetEmail, found := keyToEmail[cKey]; found { - foundEmails[targetEmail] = true - if em, _ := c["email"].(string); em != "" { - en, _ := c["enable"].(bool) - enableByEmail[em] = en - } + em, _ := c["email"].(string) + if _, found := wantedEmails[em]; found && em != "" { + foundEmails[em] = true + en, _ := c["enable"].(bool) + enableByEmail[em] = en continue } newClients = append(newClients, client) } - for _, email := range keyToEmail { + for email := range wantedEmails { if !foundEmails[email] { res.perEmailSkipped[email] = "Client Not Found In Inbound" } @@ -3547,16 +3507,18 @@ func (s *ClientService) Detach(inboundSvc *InboundService, id int, inboundIds [] if _, attached := have[ibId]; !attached { continue } - inbound, getErr := inboundSvc.GetInbound(ibId) - if getErr != nil { + if _, getErr := inboundSvc.GetInbound(ibId); getErr != nil { return needRestart, getErr } - key := clientKeyForProtocol(inbound.Protocol, existing) - if key == "" { + // Detach by email — the client's stable identity (see Delete). + if existing.Email == "" { continue } - nr, delErr := s.DelInboundClient(inboundSvc, ibId, key, true) + nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, true) if delErr != nil { + if errors.Is(delErr, ErrClientNotInInbound) { + continue + } return needRestart, delErr } if nr { @@ -3782,7 +3744,7 @@ func (s *ClientService) addInboundClient(inboundSvc *InboundService, data *model return needRestart, nil } -func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *model.Inbound, clientId string) (bool, error) { +func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *model.Inbound, oldEmail string) (bool, error) { defer lockInbound(data.Id).Unlock() clients, err := inboundSvc.GetClients(data) @@ -3808,56 +3770,30 @@ func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *mo return false, err } - oldEmail := "" newClientId := "" + switch oldInbound.Protocol { + case "trojan": + newClientId = clients[0].Password + case "shadowsocks": + newClientId = clients[0].Email + case "hysteria": + newClientId = clients[0].Auth + default: + newClientId = clients[0].ID + } + + // Locate the client to replace by email — the client's stable identity. + // Credentials (uuid/password/auth) can drift from the inbound JSON, so they + // are never used for matching. clientIndex := -1 for index, oldClient := range oldClients { - oldClientId := "" - switch oldInbound.Protocol { - case "trojan": - oldClientId = oldClient.Password - newClientId = clients[0].Password - case "shadowsocks": - oldClientId = oldClient.Email - newClientId = clients[0].Email - case "hysteria": - oldClientId = oldClient.Auth - newClientId = clients[0].Auth - default: - oldClientId = oldClient.ID - newClientId = clients[0].ID - } - if clientId == oldClientId { + if strings.EqualFold(oldClient.Email, oldEmail) { oldEmail = oldClient.Email clientIndex = index break } } - if clientIndex == -1 { - var rec model.ClientRecord - var lookupErr error - switch oldInbound.Protocol { - case "trojan": - lookupErr = database.GetDB().Where("password = ?", clientId).First(&rec).Error - case "shadowsocks": - lookupErr = database.GetDB().Where("email = ?", clientId).First(&rec).Error - case "hysteria": - lookupErr = database.GetDB().Where("auth = ?", clientId).First(&rec).Error - default: - lookupErr = database.GetDB().Where("uuid = ?", clientId).First(&rec).Error - } - if lookupErr == nil && rec.Email != "" { - for index, oldClient := range oldClients { - if oldClient.Email == rec.Email { - oldEmail = oldClient.Email - clientIndex = index - break - } - } - } - } - if newClientId == "" || clientIndex == -1 { return false, common.NewError("empty client ID") } @@ -4080,145 +4016,6 @@ func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *mo return needRestart, nil } -func (s *ClientService) DelInboundClient(inboundSvc *InboundService, inboundId int, clientId string, keepTraffic bool) (bool, error) { - defer lockInbound(inboundId).Unlock() - - oldInbound, err := inboundSvc.GetInbound(inboundId) - if err != nil { - logger.Error("Load Old Data Error") - return false, err - } - var settings map[string]any - err = json.Unmarshal([]byte(oldInbound.Settings), &settings) - if err != nil { - return false, err - } - - email := "" - client_key := "id" - switch oldInbound.Protocol { - case "trojan": - client_key = "password" - case "shadowsocks": - client_key = "email" - case "hysteria": - client_key = "auth" - } - - interfaceClients := settings["clients"].([]any) - var newClients []any - needApiDel := false - clientFound := false - for _, client := range interfaceClients { - c := client.(map[string]any) - c_id := c[client_key].(string) - if c_id == clientId { - clientFound = true - email, _ = c["email"].(string) - needApiDel, _ = c["enable"].(bool) - } else { - newClients = append(newClients, client) - } - } - - if !clientFound { - return false, common.NewError("Client Not Found In Inbound For ID:", clientId) - } - - db := database.GetDB() - newClients = compactOrphans(db, newClients) - if newClients == nil { - newClients = []any{} - } - settings["clients"] = newClients - newSettings, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return false, err - } - - oldInbound.Settings = string(newSettings) - - emailShared, err := inboundSvc.emailUsedByOtherInbounds(email, inboundId) - if err != nil { - return false, err - } - - if !emailShared && !keepTraffic { - err = inboundSvc.DelClientIPs(db, email) - if err != nil { - logger.Error("Error in delete client IPs") - return false, err - } - } - needRestart := false - markDirty := false - - if len(email) > 0 { - var enables []bool - err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Limit(1).Pluck("enable", &enables).Error - if err != nil { - logger.Error("Get stats error") - return false, err - } - notDepleted := len(enables) > 0 && enables[0] - if !emailShared && !keepTraffic { - err = inboundSvc.DelClientStat(db, email) - if err != nil { - logger.Error("Delete stats Data Error") - return false, err - } - } - if needApiDel && notDepleted && oldInbound.NodeID == nil { - rt, rterr := inboundSvc.runtimeFor(oldInbound) - if rterr != nil { - needRestart = true - } else { - err1 := rt.RemoveUser(context.Background(), oldInbound, email) - if err1 == nil { - logger.Debug("Client deleted on", rt.Name(), ":", email) - needRestart = false - } else if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) { - logger.Debug("User is already deleted. Nothing to do more...") - } else { - logger.Debug("Error in deleting client on", rt.Name(), ":", err1) - needRestart = true - } - } - } - } - if oldInbound.NodeID != nil && len(email) > 0 { - rt, push, dirty, perr := inboundSvc.nodePushPlan(oldInbound) - if perr != nil { - return false, perr - } - if dirty { - markDirty = true - } - if push { - if err1 := rt.DeleteUser(context.Background(), oldInbound, email); err1 != nil { - logger.Warning("Error in deleting client on", rt.Name(), ":", err1) - markDirty = true - } - } - } - if err := db.Save(oldInbound).Error; err != nil { - return false, err - } - finalClients, gcErr := inboundSvc.GetClients(oldInbound) - if gcErr != nil { - return false, gcErr - } - if err := s.SyncInbound(db, inboundId, finalClients); err != nil { - return false, err - } - if markDirty && oldInbound.NodeID != nil { - if dErr := (&NodeService{}).MarkNodeDirty(*oldInbound.NodeID); dErr != nil { - logger.Warning("mark node dirty failed:", dErr) - } - } - return needRestart, nil -} - func (s *ClientService) DelInboundClientByEmail(inboundSvc *InboundService, inboundId int, email string, keepTraffic bool) (bool, error) { defer lockInbound(inboundId).Unlock() @@ -4256,7 +4053,7 @@ func (s *ClientService) DelInboundClientByEmail(inboundSvc *InboundService, inbo } if !found { - return false, common.NewError(fmt.Sprintf("client with email %s not found", email)) + return false, fmt.Errorf("%w for email: %s", ErrClientNotInInbound, email) } db := database.GetDB() newClients = compactOrphans(db, newClients) @@ -4363,23 +4160,15 @@ func (s *ClientService) SetClientTelegramUserID(inboundSvc *InboundService, traf return false, err } - clientId := "" - + found := false for _, oldClient := range oldClients { if oldClient.Email == clientEmail { - switch inbound.Protocol { - case "trojan": - clientId = oldClient.Password - case "shadowsocks": - clientId = oldClient.Email - default: - clientId = oldClient.ID - } + found = true break } } - if len(clientId) == 0 { + if !found { return false, common.NewError("Client Not Found For Email:", clientEmail) } @@ -4404,7 +4193,7 @@ func (s *ClientService) SetClientTelegramUserID(inboundSvc *InboundService, traf return false, err } inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientId) + needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientEmail) return needRestart, err } @@ -4448,25 +4237,18 @@ func (s *ClientService) ToggleClientEnableByEmail(inboundSvc *InboundService, cl return false, false, err } - clientId := "" + found := false clientOldEnabled := false for _, oldClient := range oldClients { if oldClient.Email == clientEmail { - switch inbound.Protocol { - case "trojan": - clientId = oldClient.Password - case "shadowsocks": - clientId = oldClient.Email - default: - clientId = oldClient.ID - } + found = true clientOldEnabled = oldClient.Enable break } } - if len(clientId) == 0 { + if !found { return false, false, common.NewError("Client Not Found For Email:", clientEmail) } @@ -4492,7 +4274,7 @@ func (s *ClientService) ToggleClientEnableByEmail(inboundSvc *InboundService, cl } inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientId) + needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientEmail) if err != nil { return false, needRestart, err } @@ -4529,23 +4311,15 @@ func (s *ClientService) ResetClientIpLimitByEmail(inboundSvc *InboundService, cl return false, err } - clientId := "" - + found := false for _, oldClient := range oldClients { if oldClient.Email == clientEmail { - switch inbound.Protocol { - case "trojan": - clientId = oldClient.Password - case "shadowsocks": - clientId = oldClient.Email - default: - clientId = oldClient.ID - } + found = true break } } - if len(clientId) == 0 { + if !found { return false, common.NewError("Client Not Found For Email:", clientEmail) } @@ -4570,7 +4344,7 @@ func (s *ClientService) ResetClientIpLimitByEmail(inboundSvc *InboundService, cl return false, err } inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientId) + needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientEmail) return needRestart, err } @@ -4588,23 +4362,15 @@ func (s *ClientService) ResetClientExpiryTimeByEmail(inboundSvc *InboundService, return false, err } - clientId := "" - + found := false for _, oldClient := range oldClients { if oldClient.Email == clientEmail { - switch inbound.Protocol { - case "trojan": - clientId = oldClient.Password - case "shadowsocks": - clientId = oldClient.Email - default: - clientId = oldClient.ID - } + found = true break } } - if len(clientId) == 0 { + if !found { return false, common.NewError("Client Not Found For Email:", clientEmail) } @@ -4629,7 +4395,7 @@ func (s *ClientService) ResetClientExpiryTimeByEmail(inboundSvc *InboundService, return false, err } inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientId) + needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientEmail) return needRestart, err } @@ -4650,23 +4416,15 @@ func (s *ClientService) ResetClientTrafficLimitByEmail(inboundSvc *InboundServic return false, err } - clientId := "" - + found := false for _, oldClient := range oldClients { if oldClient.Email == clientEmail { - switch inbound.Protocol { - case "trojan": - clientId = oldClient.Password - case "shadowsocks": - clientId = oldClient.Email - default: - clientId = oldClient.ID - } + found = true break } } - if len(clientId) == 0 { + if !found { return false, common.NewError("Client Not Found For Email:", clientEmail) } @@ -4691,6 +4449,6 @@ func (s *ClientService) ResetClientTrafficLimitByEmail(inboundSvc *InboundServic return false, err } inbound.Settings = string(modifiedSettings) - needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientId) + needRestart, err := s.UpdateInboundClient(inboundSvc, inbound, clientEmail) return needRestart, err } diff --git a/web/service/inbound.go b/web/service/inbound.go index ba6e2dc0b..05afa757f 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1402,25 +1402,11 @@ func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inb return nil } -func (s *InboundService) getClientPrimaryKey(protocol model.Protocol, client model.Client) string { - switch protocol { - case model.Trojan: - return client.Password - case model.Shadowsocks: - return client.Email - case model.Hysteria: - return client.Auth - default: - return client.ID - } -} - -func (s *InboundService) writeBackClientSubID(sourceInboundID int, sourceProtocol model.Protocol, client model.Client, subID string) (bool, error) { +func (s *InboundService) writeBackClientSubID(sourceInboundID int, client model.Client, subID string) (bool, error) { client.SubID = subID client.UpdatedAt = time.Now().UnixMilli() - clientID := s.getClientPrimaryKey(sourceProtocol, client) - if clientID == "" { - return false, common.NewError("empty client ID") + if client.Email == "" { + return false, common.NewError("empty client email") } settingsBytes, err := json.Marshal(map[string][]model.Client{ @@ -1434,7 +1420,7 @@ func (s *InboundService) writeBackClientSubID(sourceInboundID int, sourceProtoco Id: sourceInboundID, Settings: string(settingsBytes), } - return s.clientService.UpdateInboundClient(s, updatePayload, clientID) + return s.clientService.UpdateInboundClient(s, updatePayload, client.Email) } func (s *InboundService) generateRandomCredential(targetProtocol model.Protocol) string { @@ -1554,7 +1540,7 @@ func (s *InboundService) CopyInboundClients(targetInboundID int, sourceInboundID if sourceClient.SubID == "" { newSubID := uuid.NewString() - subNeedRestart, subErr := s.writeBackClientSubID(sourceInbound.Id, sourceInbound.Protocol, sourceClient, newSubID) + subNeedRestart, subErr := s.writeBackClientSubID(sourceInbound.Id, sourceClient, newSubID) if subErr != nil { result.Errors = append(result.Errors, fmt.Sprintf("%s: failed to write source subId: %v", originalEmail, subErr)) continue diff --git a/web/service/sync_scale_postgres_test.go b/web/service/sync_scale_postgres_test.go index 35a990abf..a7d7b304e 100644 --- a/web/service/sync_scale_postgres_test.go +++ b/web/service/sync_scale_postgres_test.go @@ -215,10 +215,10 @@ func TestAddDelClientPostgresScale(t *testing.T) { } addDur := time.Since(start) - delId := clients[n/2].ID + delEmail := clients[n/2].Email start = time.Now() - if _, err := svc.DelInboundClient(inboundSvc, ib.Id, delId, false); err != nil { - t.Fatalf("DelInboundClient: %v", err) + if _, err := svc.DelInboundClientByEmail(inboundSvc, ib.Id, delEmail, false); err != nil { + t.Fatalf("DelInboundClientByEmail: %v", err) } delDur := time.Since(start)