diff --git a/test/context/bridge.go b/test/context/bridge.go index 4b088519..699460ef 100644 --- a/test/context/bridge.go +++ b/test/context/bridge.go @@ -24,6 +24,7 @@ import ( "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/preferences" "github.com/ProtonMail/proton-bridge/pkg/listener" + "github.com/ProtonMail/proton-bridge/pkg/pmapi" ) // GetBridge returns bridge instance. @@ -59,7 +60,7 @@ func newBridgeInstance( cfg *fakeConfig, credStore bridge.CredentialsStorer, eventListener listener.Listener, - clientManager bridge.ClientManager, + clientManager *pmapi.ClientManager, ) *bridge.Bridge { version := os.Getenv("VERSION") bridge.UpdateCurrentUserAgent(version, runtime.GOOS, "", "") diff --git a/test/context/context.go b/test/context/context.go index d9222bf7..32530724 100644 --- a/test/context/context.go +++ b/test/context/context.go @@ -35,17 +35,20 @@ type server interface { // TestContext manages a bridge test (mocked API, bridge instance, IMAP/SMTP servers, setup steps). type TestContext struct { // Base setup for the whole bridge (core & imap & smtp). - t *bddT - cfg *fakeConfig - listener listener.Listener - pmapiController PMAPIController // pmapiController is used to create pmapi clients (either real or fake) and control server state. - testAccounts *accounts.TestAccounts + t *bddT + cfg *fakeConfig + listener listener.Listener + testAccounts *accounts.TestAccounts + + // pmapiController is used to control real or fake pmapi clients. + // The clients are created by the clientManager. + pmapiController PMAPIController + clientManager *pmapi.ClientManager // Bridge core related variables. bridge *bridge.Bridge bridgeLastError error credStore bridge.CredentialsStorer - clientManager *pmapi.ClientManager // IMAP related variables. imapAddr string diff --git a/test/fakeapi/attachments.go b/test/fakeapi/attachments.go index e4a34b96..32a5b37a 100644 --- a/test/fakeapi/attachments.go +++ b/test/fakeapi/attachments.go @@ -46,10 +46,9 @@ func (api *FakePMAPI) CreateAttachment(attachment *pmapi.Attachment, data io.Rea return attachment, nil } -func (api *FakePMAPI) DeleteAttachment(attachmentID string) error { - if err := api.checkAndRecordCall(GET, "/attachments/"+attachmentID, nil); err != nil { +func (api *FakePMAPI) DeleteAttachment(attID string) error { + if err := api.checkAndRecordCall(DELETE, "/attachments/"+attID, nil); err != nil { return err } - return nil } diff --git a/test/fakeapi/auth.go b/test/fakeapi/auth.go index 95bf911d..901fa72e 100644 --- a/test/fakeapi/auth.go +++ b/test/fakeapi/auth.go @@ -142,7 +142,7 @@ func (api *FakePMAPI) AuthRefresh(token string) (*pmapi.Auth, error) { } func (api *FakePMAPI) Logout() { - _ = api.DeleteAuth() + api.DeleteAuth() api.ClearData() } @@ -150,14 +150,12 @@ func (api *FakePMAPI) DeleteAuth() error { if err := api.checkAndRecordCall(DELETE, "/auth", nil); err != nil { return err } - // Logout will also emit change to auth channel api.sendAuth(nil) - + api.controller.deleteSession(api.uid) return nil } func (api *FakePMAPI) ClearData() { - api.controller.deleteSession(api.uid) api.unsetUser() } diff --git a/test/fakeapi/controller.go b/test/fakeapi/controller.go index 023bbb86..99138c1c 100644 --- a/test/fakeapi/controller.go +++ b/test/fakeapi/controller.go @@ -44,8 +44,8 @@ type Controller struct { log *logrus.Entry } -func NewController(cm *pmapi.ClientManager) (cntrl *Controller) { - cntrl = &Controller{ +func NewController(cm *pmapi.ClientManager) *Controller { + controller := &Controller{ lock: &sync.RWMutex{}, fakeAPIs: []*FakePMAPI{}, calls: []*fakeCall{}, @@ -64,10 +64,10 @@ func NewController(cm *pmapi.ClientManager) (cntrl *Controller) { } cm.SetClientConstructor(func(userID string) pmapi.Client { - fakeAPI := New(cntrl) - cntrl.fakeAPIs = append(cntrl.fakeAPIs, fakeAPI) + fakeAPI := New(controller) + controller.fakeAPIs = append(controller.fakeAPIs, fakeAPI) return fakeAPI }) - return + return controller } diff --git a/test/fakeapi/controller_calls.go b/test/fakeapi/controller_calls.go index eb012d1a..f99a76e6 100644 --- a/test/fakeapi/controller_calls.go +++ b/test/fakeapi/controller_calls.go @@ -39,9 +39,9 @@ type fakeCall struct { request []byte } -func (cntrl *Controller) recordCall(method method, path string, req interface{}) { - cntrl.lock.Lock() - defer cntrl.lock.Unlock() +func (ctl *Controller) recordCall(method method, path string, req interface{}) { + ctl.lock.Lock() + defer ctl.lock.Unlock() request := []byte{} if req != nil { @@ -51,16 +51,16 @@ func (cntrl *Controller) recordCall(method method, path string, req interface{}) panic(err) } } - cntrl.calls = append(cntrl.calls, &fakeCall{ + ctl.calls = append(ctl.calls, &fakeCall{ method: method, path: path, request: request, }) } -func (cntrl *Controller) PrintCalls() { +func (ctl *Controller) PrintCalls() { fmt.Println("API calls:") - for idx, call := range cntrl.calls { + for idx, call := range ctl.calls { fmt.Printf("%02d: [%s] %s\n", idx+1, call.method, call.path) if call.request != nil && string(call.request) != "null" { fmt.Printf("\t%s\n", call.request) @@ -68,8 +68,8 @@ func (cntrl *Controller) PrintCalls() { } } -func (cntrl *Controller) WasCalled(method, path string, expectedRequest []byte) bool { - for _, call := range cntrl.calls { +func (ctl *Controller) WasCalled(method, path string, expectedRequest []byte) bool { + for _, call := range ctl.calls { if string(call.method) != method && call.path != path { continue } @@ -82,9 +82,9 @@ func (cntrl *Controller) WasCalled(method, path string, expectedRequest []byte) return false } -func (cntrl *Controller) GetCalls(method, path string) [][]byte { +func (ctl *Controller) GetCalls(method, path string) [][]byte { requests := [][]byte{} - for _, call := range cntrl.calls { + for _, call := range ctl.calls { if string(call.method) == method && call.path == path { requests = append(requests, call.request) } diff --git a/test/fakeapi/controller_control.go b/test/fakeapi/controller_control.go index 24f14c35..7655f452 100644 --- a/test/fakeapi/controller_control.go +++ b/test/fakeapi/controller_control.go @@ -34,33 +34,33 @@ var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals] "Drafts": pmapi.DraftLabel, } -func (cntrl *Controller) TurnInternetConnectionOff() { - cntrl.log.Warn("Turning OFF internet") - cntrl.noInternetConnection = true +func (ctl *Controller) TurnInternetConnectionOff() { + ctl.log.Warn("Turning OFF internet") + ctl.noInternetConnection = true } -func (cntrl *Controller) TurnInternetConnectionOn() { - cntrl.log.Warn("Turning ON internet") - cntrl.noInternetConnection = false +func (ctl *Controller) TurnInternetConnectionOn() { + ctl.log.Warn("Turning ON internet") + ctl.noInternetConnection = false } -func (cntrl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error { - cntrl.usersByUsername[user.Name] = &fakeUser{ +func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error { + ctl.usersByUsername[user.Name] = &fakeUser{ user: user, password: password, has2FA: twoFAEnabled, } - cntrl.addressesByUsername[user.Name] = addresses + ctl.addressesByUsername[user.Name] = addresses return nil } -func (cntrl *Controller) AddUserLabel(username string, label *pmapi.Label) error { - if _, ok := cntrl.labelsByUsername[username]; !ok { - cntrl.labelsByUsername[username] = []*pmapi.Label{} +func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error { + if _, ok := ctl.labelsByUsername[username]; !ok { + ctl.labelsByUsername[username] = []*pmapi.Label{} } labelName := getLabelNameWithoutPrefix(label.Name) - for _, existingLabel := range cntrl.labelsByUsername[username] { + for _, existingLabel := range ctl.labelsByUsername[username] { if existingLabel.Name == labelName { return fmt.Errorf("folder or label %s already exists", label.Name) } @@ -71,17 +71,17 @@ func (cntrl *Controller) AddUserLabel(username string, label *pmapi.Label) error if label.Exclusive == 1 { prefix = "folder" } - label.ID = cntrl.labelIDGenerator.next(prefix) + label.ID = ctl.labelIDGenerator.next(prefix) label.Name = labelName - cntrl.labelsByUsername[username] = append(cntrl.labelsByUsername[username], label) - cntrl.resetUsers() + ctl.labelsByUsername[username] = append(ctl.labelsByUsername[username], label) + ctl.resetUsers() return nil } -func (cntrl *Controller) GetLabelIDs(username string, labelNames []string) ([]string, error) { +func (ctl *Controller) GetLabelIDs(username string, labelNames []string) ([]string, error) { labelIDs := []string{} for _, labelName := range labelNames { - labelID, err := cntrl.getLabelID(username, labelName) + labelID, err := ctl.getLabelID(username, labelName) if err != nil { return nil, err } @@ -90,12 +90,12 @@ func (cntrl *Controller) GetLabelIDs(username string, labelNames []string) ([]st return labelIDs, nil } -func (cntrl *Controller) getLabelID(username, labelName string) (string, error) { +func (ctl *Controller) getLabelID(username, labelName string) (string, error) { if labelID, ok := systemLabelNameToID[labelName]; ok { return labelID, nil } labelName = getLabelNameWithoutPrefix(labelName) - for _, label := range cntrl.labelsByUsername[username] { + for _, label := range ctl.labelsByUsername[username] { if label.Name == labelName { return label.ID, nil } @@ -120,23 +120,23 @@ func getLabelExclusive(name string) int { return 0 } -func (cntrl *Controller) AddUserMessage(username string, message *pmapi.Message) error { - if _, ok := cntrl.messagesByUsername[username]; !ok { - cntrl.messagesByUsername[username] = []*pmapi.Message{} +func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) error { + if _, ok := ctl.messagesByUsername[username]; !ok { + ctl.messagesByUsername[username] = []*pmapi.Message{} } - message.ID = cntrl.messageIDGenerator.next("") + message.ID = ctl.messageIDGenerator.next("") message.LabelIDs = append(message.LabelIDs, pmapi.AllMailLabel) - cntrl.messagesByUsername[username] = append(cntrl.messagesByUsername[username], message) - cntrl.resetUsers() + ctl.messagesByUsername[username] = append(ctl.messagesByUsername[username], message) + ctl.resetUsers() return nil } -func (cntrl *Controller) resetUsers() { - for _, fakeAPI := range cntrl.fakeAPIs { +func (ctl *Controller) resetUsers() { + for _, fakeAPI := range ctl.fakeAPIs { _ = fakeAPI.setUser(fakeAPI.username) } } -func (cntrl *Controller) GetMessageID(username, messageIndex string) string { +func (ctl *Controller) GetMessageID(username, messageIndex string) string { return messageIndex } diff --git a/test/fakeapi/controller_session.go b/test/fakeapi/controller_session.go index 264dbb9a..f08c6781 100644 --- a/test/fakeapi/controller_session.go +++ b/test/fakeapi/controller_session.go @@ -29,9 +29,9 @@ type fakeSession struct { var errWrongNameOrPassword = errors.New("Incorrect login credentials. Please try again") //nolint[stylecheck] -func (cntrl *Controller) createSessionIfAuthorized(username, password string) (*fakeSession, error) { +func (ctl *Controller) createSessionIfAuthorized(username, password string) (*fakeSession, error) { // get user - user, ok := cntrl.usersByUsername[username] + user, ok := ctl.usersByUsername[username] if !ok || user.password != password { return nil, errWrongNameOrPassword } @@ -39,19 +39,19 @@ func (cntrl *Controller) createSessionIfAuthorized(username, password string) (* // create session session := &fakeSession{ username: username, - uid: cntrl.tokenGenerator.next("uid"), + uid: ctl.tokenGenerator.next("uid"), hasFullScope: !user.has2FA, } - cntrl.refreshTheTokensForSession(session) + ctl.refreshTheTokensForSession(session) - cntrl.sessionsByUID[session.uid] = session + ctl.sessionsByUID[session.uid] = session return session, nil } -func (cntrl *Controller) refreshTheTokensForSession(session *fakeSession) { - session.refreshToken = cntrl.tokenGenerator.next("refresh") +func (ctl *Controller) refreshTheTokensForSession(session *fakeSession) { + session.refreshToken = ctl.tokenGenerator.next("refresh") } -func (cntrl *Controller) deleteSession(uid string) { - delete(cntrl.sessionsByUID, uid) +func (ctl *Controller) deleteSession(uid string) { + delete(ctl.sessionsByUID, uid) } diff --git a/test/fakeapi/fakeapi.go b/test/fakeapi/fakeapi.go index adb659b5..c5caacfd 100644 --- a/test/fakeapi/fakeapi.go +++ b/test/fakeapi/fakeapi.go @@ -103,6 +103,7 @@ func (api *FakePMAPI) checkInternetAndRecordCall(method method, path string, req return nil } +// TODO: This should be sent back to the ClientManager properly! func (api *FakePMAPI) sendAuth(auth *pmapi.Auth) { if auth != nil { auth.DANGEROUSLYSetUID(api.uid) diff --git a/test/fakeapi/reports.go b/test/fakeapi/reports.go index ce39335b..7b8175eb 100644 --- a/test/fakeapi/reports.go +++ b/test/fakeapi/reports.go @@ -43,6 +43,6 @@ func (api *FakePMAPI) SendSimpleMetric(category, action, label string) error { return api.checkInternetAndRecordCall(GET, "/metrics?"+v.Encode(), nil) } -func (api *FakePMAPI) ReportSentryCrash(reportErr error) (err error) { +func (api *FakePMAPI) ReportSentryCrash(err error) error { return nil } diff --git a/test/fakeapi/user.go b/test/fakeapi/user.go index 2cb1a9b4..2b916991 100644 --- a/test/fakeapi/user.go +++ b/test/fakeapi/user.go @@ -51,6 +51,9 @@ func (api *FakePMAPI) UpdateUser() (*pmapi.User, error) { } func (api *FakePMAPI) GetAddresses() (pmapi.AddressList, error) { + if err := api.checkAndRecordCall(GET, "/addresses", nil); err != nil { + return nil, err + } return *api.addresses, nil } diff --git a/test/features/bridge/relogin.feature b/test/features/bridge/relogin.feature index d874cc86..aeb4dd06 100644 --- a/test/features/bridge/relogin.feature +++ b/test/features/bridge/relogin.feature @@ -3,7 +3,7 @@ Feature: Re-login to bridge Given there is connected user "user" And there is database file for "user" When "user" logs in to bridge - Then bridge response is "failed to finish login: user is already logged in" + Then bridge response is "failed to finish login: user is already connected" And "user" is connected And "user" has running event loop @@ -12,7 +12,7 @@ Feature: Re-login to bridge Given there is connected user "user" And there is no database file for "user" When "user" logs in to bridge - Then bridge response is "failed to finish login: user is already logged in" + Then bridge response is "failed to finish login: user is already connected" And "user" is connected And "user" has database file And "user" has running event loop diff --git a/test/liveapi/calls.go b/test/liveapi/calls.go index dc93ff97..e88c581e 100644 --- a/test/liveapi/calls.go +++ b/test/liveapi/calls.go @@ -29,20 +29,20 @@ type fakeCall struct { request []byte } -func (cntrl *Controller) recordCall(method, path string, request []byte) { - cntrl.lock.Lock() - defer cntrl.lock.Unlock() +func (ctl *Controller) recordCall(method, path string, request []byte) { + ctl.lock.Lock() + defer ctl.lock.Unlock() - cntrl.calls = append(cntrl.calls, &fakeCall{ + ctl.calls = append(ctl.calls, &fakeCall{ method: method, path: path, request: request, }) } -func (cntrl *Controller) PrintCalls() { +func (ctl *Controller) PrintCalls() { fmt.Println("API calls:") - for idx, call := range cntrl.calls { + for idx, call := range ctl.calls { fmt.Printf("%02d: [%s] %s\n", idx+1, call.method, call.path) if call.request != nil && string(call.request) != "null" { fmt.Printf("\t%s\n", call.request) @@ -50,8 +50,8 @@ func (cntrl *Controller) PrintCalls() { } } -func (cntrl *Controller) WasCalled(method, path string, expectedRequest []byte) bool { - for _, call := range cntrl.calls { +func (ctl *Controller) WasCalled(method, path string, expectedRequest []byte) bool { + for _, call := range ctl.calls { if call.method != method && call.path != path { continue } @@ -64,9 +64,9 @@ func (cntrl *Controller) WasCalled(method, path string, expectedRequest []byte) return false } -func (cntrl *Controller) GetCalls(method, path string) [][]byte { +func (ctl *Controller) GetCalls(method, path string) [][]byte { requests := [][]byte{} - for _, call := range cntrl.calls { + for _, call := range ctl.calls { if call.method == method && call.path == path { requests = append(requests, call.request) } diff --git a/test/liveapi/controller.go b/test/liveapi/controller.go index 1b3ba52c..221ba639 100644 --- a/test/liveapi/controller.go +++ b/test/liveapi/controller.go @@ -28,6 +28,7 @@ type Controller struct { // Internal states. lock *sync.RWMutex calls []*fakeCall + pmapiByUsername map[string]pmapi.Client messageIDsByUsername map[string][]string clientManager *pmapi.ClientManager @@ -35,10 +36,11 @@ type Controller struct { noInternetConnection bool } -func NewController(cm *pmapi.ClientManager) (cntrl *Controller) { - cntrl = &Controller{ +func NewController(cm *pmapi.ClientManager) *Controller { + controller := &Controller{ lock: &sync.RWMutex{}, calls: []*fakeCall{}, + pmapiByUsername: map[string]pmapi.Client{}, messageIDsByUsername: map[string][]string{}, clientManager: cm, @@ -46,9 +48,9 @@ func NewController(cm *pmapi.ClientManager) (cntrl *Controller) { } cm.SetRoundTripper(&fakeTransport{ - cntrl: cntrl, + ctl: controller, transport: http.DefaultTransport, }) - return + return controller } diff --git a/test/liveapi/labels.go b/test/liveapi/labels.go index b715516c..6dcb0a4c 100644 --- a/test/liveapi/labels.go +++ b/test/liveapi/labels.go @@ -35,8 +35,12 @@ var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals] "Drafts": pmapi.DraftLabel, } -func (cntrl *Controller) AddUserLabel(username string, label *pmapi.Label) error { - client := cntrl.clientManager.GetClient(username) +func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error { + client, ok := ctl.pmapiByUsername[username] + if !ok { + return fmt.Errorf("user %s does not exist", username) + } + label.Exclusive = getLabelExclusive(label.Name) label.Name = getLabelNameWithoutPrefix(label.Name) label.Color = pmapi.LabelColors[0] @@ -46,10 +50,10 @@ func (cntrl *Controller) AddUserLabel(username string, label *pmapi.Label) error return nil } -func (cntrl *Controller) GetLabelIDs(username string, labelNames []string) ([]string, error) { +func (ctl *Controller) GetLabelIDs(username string, labelNames []string) ([]string, error) { labelIDs := []string{} for _, labelName := range labelNames { - labelID, err := cntrl.getLabelID(username, labelName) + labelID, err := ctl.getLabelID(username, labelName) if err != nil { return nil, err } @@ -58,12 +62,16 @@ func (cntrl *Controller) GetLabelIDs(username string, labelNames []string) ([]st return labelIDs, nil } -func (cntrl *Controller) getLabelID(username, labelName string) (string, error) { +func (ctl *Controller) getLabelID(username, labelName string) (string, error) { if labelID, ok := systemLabelNameToID[labelName]; ok { return labelID, nil } - client := cntrl.clientManager.GetClient(username) + client, ok := ctl.pmapiByUsername[username] + if !ok { + return "", fmt.Errorf("user %s does not exist", username) + } + labels, err := client.ListLabels() if err != nil { return "", errors.Wrap(err, "failed to list labels") diff --git a/test/liveapi/messages.go b/test/liveapi/messages.go index 3dcc8db7..c7bdab80 100644 --- a/test/liveapi/messages.go +++ b/test/liveapi/messages.go @@ -30,8 +30,11 @@ import ( "github.com/pkg/errors" ) -func (cntrl *Controller) AddUserMessage(username string, message *pmapi.Message) error { - client := cntrl.clientManager.GetClient(username) +func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) error { + client, ok := ctl.pmapiByUsername[username] + if !ok { + return fmt.Errorf("user %s does not exist", username) + } body, err := buildMessage(client, message) if err != nil { @@ -55,7 +58,7 @@ func (cntrl *Controller) AddUserMessage(username string, message *pmapi.Message) if result.Error != nil { return errors.Wrap(result.Error, "failed to import message") } - cntrl.messageIDsByUsername[username] = append(cntrl.messageIDsByUsername[username], result.MessageID) + ctl.messageIDsByUsername[username] = append(ctl.messageIDsByUsername[username], result.MessageID) } return nil @@ -122,10 +125,10 @@ func buildMessageBody(message *pmapi.Message, body *bytes.Buffer) error { return nil } -func (cntrl *Controller) GetMessageID(username, messageIndex string) string { +func (ctl *Controller) GetMessageID(username, messageIndex string) string { idx, err := strconv.Atoi(messageIndex) if err != nil { panic(fmt.Sprintf("message index %s not found", messageIndex)) } - return cntrl.messageIDsByUsername[username][idx-1] + return ctl.messageIDsByUsername[username][idx-1] } diff --git a/test/liveapi/transport.go b/test/liveapi/transport.go index 39325314..dd001e21 100644 --- a/test/liveapi/transport.go +++ b/test/liveapi/transport.go @@ -24,21 +24,21 @@ import ( "github.com/pkg/errors" ) -func (cntrl *Controller) TurnInternetConnectionOff() { - cntrl.noInternetConnection = true +func (ctl *Controller) TurnInternetConnectionOff() { + ctl.noInternetConnection = true } -func (cntrl *Controller) TurnInternetConnectionOn() { - cntrl.noInternetConnection = false +func (ctl *Controller) TurnInternetConnectionOn() { + ctl.noInternetConnection = false } type fakeTransport struct { - cntrl *Controller + ctl *Controller transport http.RoundTripper } func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if t.cntrl.noInternetConnection { + if t.ctl.noInternetConnection { return nil, errors.New("no route to host") } @@ -53,7 +53,7 @@ func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) { return nil, errors.Wrap(err, "failed to read body") } } - t.cntrl.recordCall(req.Method, req.URL.Path, body) + t.ctl.recordCall(req.Method, req.URL.Path, body) return t.transport.RoundTrip(req) } diff --git a/test/liveapi/users.go b/test/liveapi/users.go index da1c045b..1aa70a6f 100644 --- a/test/liveapi/users.go +++ b/test/liveapi/users.go @@ -23,12 +23,12 @@ import ( "github.com/pkg/errors" ) -func (cntrl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error { +func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error { if twoFAEnabled { return godog.ErrPending } - client := cntrl.clientManager.GetClient(user.ID) + client := ctl.clientManager.GetClient(user.ID) authInfo, err := client.AuthInfo(user.Name) if err != nil { @@ -54,5 +54,7 @@ func (cntrl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, return errors.Wrap(err, "failed to clean user") } + ctl.pmapiByUsername[user.Name] = client + return nil }