diff --git a/api/forgot_test.go b/api/forgot_test.go index b685e86e5..09f594b85 100644 --- a/api/forgot_test.go +++ b/api/forgot_test.go @@ -30,7 +30,7 @@ func TestForgotResponds(t *testing.T) { method: "PUT", url: "/accept/forgot", respCode: 200, - body: jo{ + body: testJSONObject{ "key": "1234_aK3yxxx123", "email": "me@myemail.com", "password": "myN3wpa55w0rd", @@ -48,7 +48,7 @@ func TestForgotResponds(t *testing.T) { method: "PUT", url: "/accept/forgot", respCode: 404, - body: jo{ + body: testJSONObject{ "key": "1234_no_match", "email": "me@myemail.com", "password": "myN3wpa55w0rd", @@ -88,7 +88,7 @@ func TestForgotResponds(t *testing.T) { if response.Body.Len() != 0 && len(test.response) != 0 { // compare bodies by comparing the unmarshalled JSON results - var result = &jo{} + var result = &testJSONObject{} if err := json.NewDecoder(response.Body).Decode(result); err != nil { t.Logf("Err decoding nonempty response body: [%v]\n [%v]\n", err, response.Body) diff --git a/api/hydrophoneApi.go b/api/hydrophoneApi.go index a8eeae22d..2313af47d 100644 --- a/api/hydrophoneApi.go +++ b/api/hydrophoneApi.go @@ -32,7 +32,7 @@ type ( } Config struct { ServerSecret string `json:"serverSecret"` //used for services - WebURL string `json:"webUrl"` + WebURL string `json:"webUrl"` AssetURL string `json:"assetUrl"` } @@ -256,7 +256,7 @@ func (a *Api) token(res http.ResponseWriter, req *http.Request) *shoreline.Token td := a.sl.CheckToken(token) if td == nil { - statusErr := &status.StatusError{status.NewStatus(http.StatusForbidden, STATUS_INVALID_TOKEN)} + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusForbidden, STATUS_INVALID_TOKEN)} log.Printf("token %s err[%v] ", STATUS_INVALID_TOKEN, statusErr) a.sendModelAsResWithStatus(res, statusErr, http.StatusForbidden) return nil @@ -264,7 +264,7 @@ func (a *Api) token(res http.ResponseWriter, req *http.Request) *shoreline.Token //all good! return td } - statusErr := &status.StatusError{status.NewStatus(http.StatusUnauthorized, STATUS_NO_TOKEN)} + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusUnauthorized, STATUS_NO_TOKEN)} log.Printf("token %s err[%v] ", STATUS_NO_TOKEN, statusErr) a.sendModelAsResWithStatus(res, statusErr, http.StatusUnauthorized) return nil diff --git a/api/hydrophoneApi_test.go b/api/hydrophoneApi_test.go index f380cae7d..d5fc43c3d 100644 --- a/api/hydrophoneApi_test.go +++ b/api/hydrophoneApi_test.go @@ -97,20 +97,21 @@ func (m *testingShorelingMock) CheckToken(token string) *shoreline.TokenData { type ( //common test structure toTest struct { + desc string skip bool returnNone bool method string url string - body jo + body testJSONObject token string respCode int - response jo + response testJSONObject } // These two types make it easier to define blobs of json inline. // We don't use the types defined by the API because we want to // be able to test with partial data structures. - // jo is a generic json object - jo map[string]interface{} + // testJSONObject is a generic json object + testJSONObject map[string]interface{} // and ja is a generic json array ja []interface{} @@ -153,10 +154,10 @@ func TestGetStatus_StatusInternalServerError(t *testing.T) { } } -func (i *jo) deepCompare(j *jo) string { - for k, _ := range *i { +func (i *testJSONObject) deepCompare(j *testJSONObject) string { + for k := range *i { if reflect.DeepEqual((*i)[k], (*j)[k]) == false { - return fmt.Sprintf("for [%s] was [%v] expected [%v] ", k, (*i)[k], (*j)[k]) + return fmt.Sprintf("`%s` expected `%v` actual `%v` ", k, (*j)[k], (*i)[k]) } } return "" diff --git a/api/invite.go b/api/invite.go index 0ffd27575..69ac62805 100644 --- a/api/invite.go +++ b/api/invite.go @@ -13,10 +13,11 @@ import ( const ( //Status message we return from the service - STATUS_EXISTING_INVITE = "There is already an existing invite" - STATUS_EXISTING_MEMBER = "The user is already an existing member" - STATUS_INVITE_NOT_FOUND = "No matching invite was found" - STATUS_INVITE_CANCELED = "Invite has been canceled" + statusExistingInviteMessage = "There is already an existing invite" + statusExistingMemberMessage = "The user is already an existing member" + statusInviteNotFoundMessage = "No matching invite was found" + statusInviteCanceledMessage = "Invite has been canceled" + statusForbiddenMessage = "Forbidden to perform requested operation" ) type ( @@ -29,11 +30,11 @@ type ( //Checks do they have an existing invite or are they already a team member //Or are they an existing user and already in the group? -func (a *Api) checkForDuplicateInvite(inviteeEmail, invitorId, token string, res http.ResponseWriter) (bool, *shoreline.UserData) { +func (a *Api) checkForDuplicateInvite(inviteeEmail, invitorID, token string, res http.ResponseWriter) (bool, *shoreline.UserData) { //already has invite from this user? invites, _ := a.Store.FindConfirmations( - &models.Confirmation{CreatorId: invitorId, Email: inviteeEmail, Type: models.TypeCareteamInvite}, + &models.Confirmation{CreatorId: invitorID, Email: inviteeEmail, Type: models.TypeCareteamInvite}, models.StatusPending, ) @@ -41,9 +42,9 @@ func (a *Api) checkForDuplicateInvite(inviteeEmail, invitorId, token string, res //rule is we cannot send if the invite is not yet expired if !invites[0].IsExpired() { - log.Println(STATUS_EXISTING_INVITE) + log.Println(statusExistingInviteMessage) log.Println("last invite not yet expired") - statusErr := &status.StatusError{status.NewStatus(http.StatusConflict, STATUS_EXISTING_INVITE)} + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusConflict, statusExistingInviteMessage)} a.sendModelAsResWithStatus(res, statusErr, http.StatusConflict) return true, nil } @@ -53,11 +54,11 @@ func (a *Api) checkForDuplicateInvite(inviteeEmail, invitorId, token string, res invitedUsr := a.findExistingUser(inviteeEmail, a.sl.TokenProvide()) if invitedUsr != nil && invitedUsr.UserID != "" { - if perms, err := a.gatekeeper.UserInGroup(invitedUsr.UserID, invitorId); err != nil { + if perms, err := a.gatekeeper.UserInGroup(invitedUsr.UserID, invitorID); err != nil { log.Printf("error checking if user is in group [%v]", err) } else if perms != nil { - log.Println(STATUS_EXISTING_MEMBER) - statusErr := &status.StatusError{status.NewStatus(http.StatusConflict, STATUS_EXISTING_MEMBER)} + log.Println(statusExistingMemberMessage) + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusConflict, statusExistingMemberMessage)} a.sendModelAsResWithStatus(res, statusErr, http.StatusConflict) return true, invitedUsr } @@ -73,20 +74,20 @@ func (a *Api) checkForDuplicateInvite(inviteeEmail, invitorId, token string, res // status: 400 func (a *Api) GetReceivedInvitations(res http.ResponseWriter, req *http.Request, vars map[string]string) { if token := a.token(res, req); token != nil { - inviteeId := vars["userid"] + inviteeID := vars["userid"] - if inviteeId == "" { + if inviteeID == "" { res.WriteHeader(http.StatusBadRequest) return } // Non-server tokens only legit when for same userid - if !token.IsServer && inviteeId != token.UserID { + if !token.IsServer && inviteeID != token.UserID { log.Printf("GetReceivedInvitations %s ", STATUS_UNAUTHORIZED) a.sendModelAsResWithStatus(res, status.StatusError{status.NewStatus(http.StatusUnauthorized, STATUS_UNAUTHORIZED)}, http.StatusUnauthorized) return } - invitedUsr := a.findExistingUser(inviteeId, req.Header.Get(TP_SESSION_TOKEN)) + invitedUsr := a.findExistingUser(inviteeID, req.Header.Get(TP_SESSION_TOKEN)) //find all oustanding invites were this user is the invite// found, err := a.Store.FindConfirmations(&models.Confirmation{Email: invitedUsr.Emails[0], Type: models.TypeCareteamInvite}, models.StatusPending) @@ -97,7 +98,7 @@ func (a *Api) GetReceivedInvitations(res http.ResponseWriter, req *http.Request, } if invites := a.checkFoundConfirmations(res, found, err); invites != nil { - a.ensureIdSet(inviteeId, invites) + a.ensureIdSet(inviteeID, invites) log.Printf("GetReceivedInvitations: found and have checked [%d] invites ", len(invites)) a.logMetric("get received invites", req) a.sendModelAsResWithStatus(res, invites, http.StatusOK) @@ -116,14 +117,14 @@ func (a *Api) GetReceivedInvitations(res http.ResponseWriter, req *http.Request, func (a *Api) GetSentInvitations(res http.ResponseWriter, req *http.Request, vars map[string]string) { if token := a.token(res, req); token != nil { - invitorId := vars["userid"] + invitorID := vars["userid"] - if invitorId == "" { + if invitorID == "" { res.WriteHeader(http.StatusBadRequest) return } - if permissions, err := a.tokenUserHasRequestedPermissions(token, invitorId, commonClients.Permissions{"root": commonClients.Allowed, "custodian": commonClients.Allowed}); err != nil { + if permissions, err := a.tokenUserHasRequestedPermissions(token, invitorID, commonClients.Permissions{"root": commonClients.Allowed, "custodian": commonClients.Allowed}); err != nil { a.sendError(res, http.StatusInternalServerError, STATUS_ERR_FINDING_USR, err) return } else if permissions["root"] == nil && permissions["custodian"] == nil { @@ -132,7 +133,7 @@ func (a *Api) GetSentInvitations(res http.ResponseWriter, req *http.Request, var } //find all invites I have sent that are pending or declined - found, err := a.Store.FindConfirmations(&models.Confirmation{CreatorId: invitorId, Type: models.TypeCareteamInvite}, models.StatusPending, models.StatusDeclined) + found, err := a.Store.FindConfirmations(&models.Confirmation{CreatorId: invitorID, Type: models.TypeCareteamInvite}, models.StatusPending, models.StatusDeclined) if invitations := a.checkFoundConfirmations(res, found, err); invitations != nil { a.logMetric("get sent invites", req) a.sendModelAsResWithStatus(res, invitations, http.StatusOK) @@ -144,93 +145,125 @@ func (a *Api) GetSentInvitations(res http.ResponseWriter, req *http.Request, var //Accept the given invite // -// status: 200 when accepted -// status: 400 when the incoming data is incomplete or incorrect +// http.StatusOK when accepted +// http.StatusBadRequest when the incoming data is incomplete or incorrect +// http.StatusForbidden when mismatch of user ID's, type or status func (a *Api) AcceptInvite(res http.ResponseWriter, req *http.Request, vars map[string]string) { - if token := a.token(res, req); token != nil { - inviteeId := vars["userid"] - invitorId := vars["invitedby"] + inviteeID := vars["userid"] + invitorID := vars["invitedby"] - if inviteeId == "" || invitorId == "" { + if inviteeID == "" || invitorID == "" { + log.Printf("AcceptInvite inviteeID %s or invitorID %s not set", inviteeID, invitorID) res.WriteHeader(http.StatusBadRequest) return } // Non-server tokens only legit when for same userid - if !token.IsServer && inviteeId != token.UserID { - log.Printf("AcceptInvite %s ", STATUS_UNAUTHORIZED) - a.sendModelAsResWithStatus(res, status.StatusError{status.NewStatus(http.StatusUnauthorized, STATUS_UNAUTHORIZED)}, http.StatusUnauthorized) + if !token.IsServer && inviteeID != token.UserID { + log.Println("AcceptInvite ", STATUS_UNAUTHORIZED) + a.sendModelAsResWithStatus( + res, + status.StatusError{Status: status.NewStatus(http.StatusUnauthorized, STATUS_UNAUTHORIZED)}, + http.StatusUnauthorized, + ) return } accept := &models.Confirmation{} if err := json.NewDecoder(req.Body).Decode(accept); err != nil { - log.Printf("AcceptInvite: error decoding invite data: %v\n", err) - statusErr := &status.StatusError{status.NewStatus(http.StatusBadRequest, STATUS_ERR_DECODING_CONFIRMATION)} - a.sendModelAsResWithStatus(res, statusErr, http.StatusBadRequest) + log.Printf("AcceptInvite error decoding invite data: %v\n", err) + a.sendModelAsResWithStatus( + res, + &status.StatusError{Status: status.NewStatus(http.StatusBadRequest, STATUS_ERR_DECODING_CONFIRMATION)}, + http.StatusBadRequest, + ) return } if accept.Key == "" { + log.Println("AcceptInvite has no confirmation key set") res.WriteHeader(http.StatusBadRequest) return } - if conf, err := a.findExistingConfirmation(accept, res); err != nil { - log.Printf("AcceptInvite: finding [%s]\n", err.Error()) + conf, err := a.findExistingConfirmation(accept, res) + if err != nil { + log.Printf("AcceptInvite error while finding confirmation [%s]\n", err.Error()) a.sendModelAsResWithStatus(res, err, http.StatusInternalServerError) return - } else if conf != nil { - //New set the permissions for the invite - var permissions commonClients.Permissions - conf.DecodeContext(&permissions) - - if setPerms, err := a.gatekeeper.SetPermissions(inviteeId, invitorId, permissions); err != nil { - log.Printf("AcceptInvite: permissions [%v]\n", err) - statusErr := &status.StatusError{status.NewStatus(http.StatusInternalServerError, STATUS_ERR_DECODING_CONFIRMATION)} - a.sendModelAsResWithStatus(res, statusErr, http.StatusInternalServerError) - return - } else { - log.Printf("AcceptInvite: permissions were set as [%v] after an invite was accepted", setPerms) - //we know the user now - conf.UserId = inviteeId - - conf.UpdateStatus(models.StatusCompleted) - if a.addOrUpdateConfirmation(conf, res) { - a.logMetric("acceptinvite", req) - res.WriteHeader(http.StatusOK) - res.Write([]byte(STATUS_OK)) - return - } + } + if conf == nil { + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusNotFound, statusInviteNotFoundMessage)} + log.Println("AcceptInvite ", statusErr.Error()) + a.sendModelAsResWithStatus(res, statusErr, http.StatusNotFound) + return + } + + validationErrors := []error{} + + conf.ValidateStatus(models.StatusPending, &validationErrors). + ValidateType(models.TypeCareteamInvite, &validationErrors). + ValidateUserID(inviteeID, &validationErrors). + ValidateCreatorID(invitorID, &validationErrors) + + if len(validationErrors) > 0 { + for _, validationError := range validationErrors { + log.Println("AcceptInvite forbidden as there was a expectation mismatch", validationError) } + a.sendModelAsResWithStatus( + res, + &status.StatusError{Status: status.NewStatus(http.StatusForbidden, statusForbiddenMessage)}, + http.StatusForbidden, + ) + return } - statusErr := &status.StatusError{status.NewStatus(http.StatusNotFound, STATUS_INVITE_NOT_FOUND)} - log.Printf("AcceptInvite: [%s]", statusErr.Error()) - a.sendModelAsResWithStatus(res, statusErr, http.StatusNotFound) + + var permissions commonClients.Permissions + conf.DecodeContext(&permissions) + setPerms, err := a.gatekeeper.SetPermissions(inviteeID, invitorID, permissions) + if err != nil { + log.Printf("AcceptInvite error setting permissions [%v]\n", err) + a.sendModelAsResWithStatus( + res, + &status.StatusError{Status: status.NewStatus(http.StatusInternalServerError, STATUS_ERR_DECODING_CONFIRMATION)}, + http.StatusInternalServerError, + ) + return + } + log.Printf("AcceptInvite: permissions were set as [%v] after an invite was accepted", setPerms) + conf.UpdateStatus(models.StatusCompleted) + if !a.addOrUpdateConfirmation(conf, res) { + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusInternalServerError, STATUS_ERR_SAVING_CONFIRMATION)} + log.Println("AcceptInvite ", statusErr.Error()) + a.sendModelAsResWithStatus(res, statusErr, http.StatusInternalServerError) + return + } + a.logMetric("acceptinvite", req) + res.WriteHeader(http.StatusOK) + res.Write([]byte(STATUS_OK)) return } - return } // Cancel an invite the has been sent to an email address // // status: 200 when cancled -// status: 404 STATUS_INVITE_NOT_FOUND +// status: 404 statusInviteNotFoundMessage // status: 400 when the incoming data is incomplete or incorrect func (a *Api) CancelInvite(res http.ResponseWriter, req *http.Request, vars map[string]string) { if token := a.token(res, req); token != nil { - invitorId := vars["userid"] + invitorID := vars["userid"] email := vars["invited_address"] - if invitorId == "" || email == "" { + if invitorID == "" || email == "" { res.WriteHeader(http.StatusBadRequest) return } - if permissions, err := a.tokenUserHasRequestedPermissions(token, invitorId, commonClients.Permissions{"root": commonClients.Allowed, "custodian": commonClients.Allowed}); err != nil { + if permissions, err := a.tokenUserHasRequestedPermissions(token, invitorID, commonClients.Permissions{"root": commonClients.Allowed, "custodian": commonClients.Allowed}); err != nil { a.sendError(res, http.StatusInternalServerError, STATUS_ERR_FINDING_USR, err) return } else if permissions["root"] == nil && permissions["custodian"] == nil { @@ -240,7 +273,7 @@ func (a *Api) CancelInvite(res http.ResponseWriter, req *http.Request, vars map[ invite := &models.Confirmation{ Email: email, - CreatorId: invitorId, + CreatorId: invitorID, Creator: models.Creator{}, Type: models.TypeCareteamInvite, } @@ -258,7 +291,7 @@ func (a *Api) CancelInvite(res http.ResponseWriter, req *http.Request, vars map[ return } } - statusErr := &status.StatusError{status.NewStatus(http.StatusNotFound, STATUS_INVITE_NOT_FOUND)} + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusNotFound, statusInviteNotFoundMessage)} log.Printf("CancelInvite: [%s]", statusErr.Error()) a.sendModelAsResWithStatus(res, statusErr, http.StatusNotFound) return @@ -271,16 +304,16 @@ func (a *Api) CancelInvite(res http.ResponseWriter, req *http.Request, vars map[ func (a *Api) DismissInvite(res http.ResponseWriter, req *http.Request, vars map[string]string) { if token := a.token(res, req); token != nil { - inviteeId := vars["userid"] - invitorId := vars["invitedby"] + inviteeID := vars["userid"] + invitorID := vars["invitedby"] - if inviteeId == "" || invitorId == "" { + if inviteeID == "" || invitorID == "" { res.WriteHeader(http.StatusBadRequest) return } // Non-server tokens only legit when for same userid - if !token.IsServer && inviteeId != token.UserID { + if !token.IsServer && inviteeID != token.UserID { log.Printf("DismissInvite %s ", STATUS_UNAUTHORIZED) a.sendModelAsResWithStatus(res, status.StatusError{status.NewStatus(http.StatusUnauthorized, STATUS_UNAUTHORIZED)}, http.StatusUnauthorized) return @@ -289,7 +322,7 @@ func (a *Api) DismissInvite(res http.ResponseWriter, req *http.Request, vars map dismiss := &models.Confirmation{} if err := json.NewDecoder(req.Body).Decode(dismiss); err != nil { log.Printf("DismissInvite: error decoding invite to dismiss [%v]", err) - statusErr := &status.StatusError{status.NewStatus(http.StatusBadRequest, STATUS_ERR_DECODING_CONFIRMATION)} + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusBadRequest, STATUS_ERR_DECODING_CONFIRMATION)} a.sendModelAsResWithStatus(res, statusErr, http.StatusBadRequest) return } @@ -313,7 +346,7 @@ func (a *Api) DismissInvite(res http.ResponseWriter, req *http.Request, vars map return } } - statusErr := &status.StatusError{status.NewStatus(http.StatusNotFound, STATUS_INVITE_NOT_FOUND)} + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusNotFound, statusInviteNotFoundMessage)} log.Printf("DismissInvite: [%s]", statusErr.Error()) a.sendModelAsResWithStatus(res, statusErr, http.StatusNotFound) return @@ -324,20 +357,20 @@ func (a *Api) DismissInvite(res http.ResponseWriter, req *http.Request, vars map //Send a invite to join my team // // status: 200 models.Confirmation -// status: 409 STATUS_EXISTING_INVITE - user already has a pending or declined invite -// status: 409 STATUS_EXISTING_MEMBER - user is already part of the team +// status: 409 statusExistingInviteMessage - user already has a pending or declined invite +// status: 409 statusExistingMemberMessage - user is already part of the team // status: 400 func (a *Api) SendInvite(res http.ResponseWriter, req *http.Request, vars map[string]string) { if token := a.token(res, req); token != nil { - invitorId := vars["userid"] + invitorID := vars["userid"] - if invitorId == "" { + if invitorID == "" { res.WriteHeader(http.StatusBadRequest) return } - if permissions, err := a.tokenUserHasRequestedPermissions(token, invitorId, commonClients.Permissions{"root": commonClients.Allowed, "custodian": commonClients.Allowed}); err != nil { + if permissions, err := a.tokenUserHasRequestedPermissions(token, invitorID, commonClients.Permissions{"root": commonClients.Allowed, "custodian": commonClients.Allowed}); err != nil { a.sendError(res, http.StatusInternalServerError, STATUS_ERR_FINDING_USR, err) return } else if permissions["root"] == nil && permissions["custodian"] == nil { @@ -349,7 +382,7 @@ func (a *Api) SendInvite(res http.ResponseWriter, req *http.Request, vars map[st var ib = &inviteBody{} if err := json.NewDecoder(req.Body).Decode(ib); err != nil { log.Printf("SendInvite: error decoding invite to detail %v\n", err) - statusErr := &status.StatusError{status.NewStatus(http.StatusBadRequest, STATUS_ERR_DECODING_CONFIRMATION)} + statusErr := &status.StatusError{Status: status.NewStatus(http.StatusBadRequest, STATUS_ERR_DECODING_CONFIRMATION)} a.sendModelAsResWithStatus(res, statusErr, http.StatusBadRequest) return } @@ -359,12 +392,12 @@ func (a *Api) SendInvite(res http.ResponseWriter, req *http.Request, vars map[st return } - if existingInvite, invitedUsr := a.checkForDuplicateInvite(ib.Email, invitorId, req.Header.Get(TP_SESSION_TOKEN), res); existingInvite == true { + if existingInvite, invitedUsr := a.checkForDuplicateInvite(ib.Email, invitorID, req.Header.Get(TP_SESSION_TOKEN), res); existingInvite == true { log.Printf("SendInvite: invited [%s] user already has or had an invite", ib.Email) return } else { //None exist so lets create the invite - invite, _ := models.NewConfirmationWithContext(models.TypeCareteamInvite, models.TemplateNameCareteamInvite, invitorId, ib.Permissions) + invite, _ := models.NewConfirmationWithContext(models.TypeCareteamInvite, models.TemplateNameCareteamInvite, invitorID, ib.Permissions) invite.Email = ib.Email if invitedUsr != nil { diff --git a/api/invite_test.go b/api/invite_test.go index 4270eb45d..6f2c6f31f 100644 --- a/api/invite_test.go +++ b/api/invite_test.go @@ -23,7 +23,6 @@ func initTestingRouterNoPerms() *mux.Router { mockSeagull, mockTemplates, ) - hydrophone.SetHandlers("", testRtr) return testRtr } @@ -32,11 +31,11 @@ func TestSendInvite_NoPerms(t *testing.T) { tstRtr := initTestingRouterNoPerms() sendBody := &bytes.Buffer{} - json.NewEncoder(sendBody).Encode(jo{ + json.NewEncoder(sendBody).Encode(testJSONObject{ "email": testing_uid2 + "@email.org", - "permissions": jo{ - "view": jo{}, - "note": jo{}, + "permissions": testJSONObject{ + "view": testJSONObject{}, + "note": testJSONObject{}, }, }) @@ -113,134 +112,130 @@ func TestDismissInvite_NoPerms(t *testing.T) { func TestInviteResponds(t *testing.T) { - tests := []toTest{ + inviteTests := []toTest{ { - // can't invite without a body - method: "POST", + desc: "can't invite without a body", + method: http.MethodPost, url: fmt.Sprintf("/send/invite/%s", testing_uid1), token: testing_token_uid1, - respCode: 400, + respCode: http.StatusBadRequest, }, { - // can't invite without permissions - method: "POST", + desc: "can't invite without permissions", + method: http.MethodPost, url: fmt.Sprintf("/send/invite/%s", testing_uid1), token: testing_token_uid1, - respCode: 400, - body: jo{"email": "personToInvite@email.com"}, + respCode: http.StatusBadRequest, + body: testJSONObject{ + "email": "personToInvite@email.com", + }, }, { - // can't invite without email - method: "POST", + desc: "can't invite without email", + method: http.MethodPost, url: fmt.Sprintf("/send/invite/%s", testing_uid1), token: testing_token_uid1, respCode: http.StatusBadRequest, - body: jo{ - "permissions": jo{ - "view": jo{}, - "note": jo{}, - }, + body: testJSONObject{ + "email": "", + "permissions": testJSONObject{"view": testJSONObject{}}, }, }, { - // if duplicate invite - method: "POST", + desc: "can't have a duplicate invite", + method: http.MethodPost, url: fmt.Sprintf("/send/invite/%s", testing_uid2), token: testing_token_uid1, respCode: http.StatusConflict, - body: jo{ + body: testJSONObject{ "email": testing_uid2 + "@email.org", - "permissions": jo{ - "view": jo{}, - "note": jo{}, + "permissions": testJSONObject{ + "view": testJSONObject{}, + "note": testJSONObject{}, }, }, }, { - // but if you have them all, it should work + desc: "invite valid if email, permissons and not a duplicate", returnNone: true, - method: "POST", + method: http.MethodPost, url: fmt.Sprintf("/send/invite/%s", testing_uid2), token: testing_token_uid1, respCode: http.StatusOK, - body: jo{ - "email": testing_uid2 + "@email.org", - "permissions": jo{ - "view": jo{}, - "note": jo{}, - }, + body: testJSONObject{ + "email": testing_uid2 + "@email.org", + "permissions": testJSONObject{"view": testJSONObject{}}, }, }, { - // we should get a list of our outstanding invitations - method: "GET", + desc: "invitations gives list of our outstanding invitations", + method: http.MethodGet, url: fmt.Sprintf("/invitations/%s", testing_uid1), token: testing_token_uid1, respCode: http.StatusOK, - response: jo{ + response: testJSONObject{ "invitedBy": testing_uid2, - "permissions": jo{ - "view": jo{}, - "note": jo{}, + "permissions": testJSONObject{ + "view": testJSONObject{}, + "note": testJSONObject{}, }, }, }, { - // not found without the full path - method: "PUT", + desc: "request not found without the full path", + method: http.MethodPut, url: "/accept/invite", token: testing_token_uid1, respCode: http.StatusNotFound, }, { - // we can accept an invitation we did get - method: "PUT", - url: fmt.Sprintf("/accept/invite/%s/%s", testing_uid1, testing_uid2), + desc: "invalid request to accept an invite when user ID's not expected", + method: http.MethodPut, + url: fmt.Sprintf("/accept/invite/%s/%s", testing_uid1, "badID"), token: testing_token_uid1, - respCode: http.StatusOK, - body: jo{ + respCode: http.StatusForbidden, + body: testJSONObject{ "key": "careteam_invite/1234", }, }, { - // get invitations we sent - method: "GET", + desc: "invite will get invitations we sent", + method: http.MethodGet, url: fmt.Sprintf("/invite/%s", testing_uid2), token: testing_token_uid1, respCode: http.StatusOK, - response: jo{ + response: testJSONObject{ "email": "personToInvite@email.com", - "permissions": jo{ - "view": jo{}, - "note": jo{}, + "permissions": testJSONObject{ + "view": testJSONObject{}, + "note": testJSONObject{}, }, }, }, { - // dismiss an invitation we were sent - method: "PUT", + desc: "dismiss an invitation we were sent", + method: http.MethodPut, url: fmt.Sprintf("/dismiss/invite/%s/%s", testing_uid2, testing_uid1), token: testing_token_uid1, respCode: http.StatusOK, - body: jo{ + body: testJSONObject{ "key": "careteam_invite/1234", }, }, { - // delete the other invitation we sent - method: "PUT", - url: fmt.Sprintf("%s/invited/other@youremail.com", testing_uid1), + desc: "delete the other invitation we sent", + method: http.MethodPut, + url: fmt.Sprintf("/%s/invited/other@youremail.com", testing_uid1), token: testing_token_uid1, respCode: http.StatusOK, }, } - for idx, test := range tests { + for idx, inviteTest := range inviteTests { // don't run a test if it says to skip it - if test.skip { + if inviteTest.skip { continue } - //fresh each time var testRtr = mux.NewRouter() //default flow, fully authorized @@ -256,7 +251,7 @@ func TestInviteResponds(t *testing.T) { ) //testing when there is nothing to return from the store - if test.returnNone { + if inviteTest.returnNone { hydrophone = InitApi( FAKE_CONFIG, mockStoreEmpty, @@ -273,32 +268,35 @@ func TestInviteResponds(t *testing.T) { var body = &bytes.Buffer{} // build the body only if there is one defined in the test - if len(test.body) != 0 { - json.NewEncoder(body).Encode(test.body) + if len(inviteTest.body) != 0 { + json.NewEncoder(body).Encode(inviteTest.body) } - request, _ := http.NewRequest(test.method, test.url, body) - if test.token != "" { + request, _ := http.NewRequest(inviteTest.method, inviteTest.url, body) + if inviteTest.token != "" { request.Header.Set(TP_SESSION_TOKEN, testing_token) } response := httptest.NewRecorder() testRtr.ServeHTTP(response, request) - if response.Code != test.respCode { - t.Logf("TestId %d expected %d actual %d", idx, test.respCode, response.Code) + if response.Code != inviteTest.respCode { + t.Logf("TestId `%d` `%s` expected `%d` actual `%d`", idx, inviteTest.desc, inviteTest.respCode, response.Code) t.Fail() } - if response.Body.Len() != 0 && len(test.response) != 0 { - // compare bodies by comparing the unmarshalled JSON results - var result = &jo{} - - if err := json.NewDecoder(response.Body).Decode(result); err != nil { - t.Logf("Err decoding nonempty response body: [%v]\n [%v]\n", err, response.Body) - return + if response.Body.Len() != 0 && len(inviteTest.response) != 0 { + var result = &testJSONObject{} + err := json.NewDecoder(response.Body).Decode(result) + if err != nil { + //TODO: not dealing with arrays at the moment .... + if err.Error() != "json: cannot unmarshal array into Go value of type api.testJSONObject" { + t.Logf("TestId `%d` `%s` errored `%s` body `%v`", idx, inviteTest.desc, err.Error(), response.Body) + t.Fail() + } } - if cmp := result.deepCompare(&test.response); cmp != "" { - t.Fatalf("Test %d url: '%s'\n\t%s\n", idx, test.url, cmp) + if cmp := result.deepCompare(&inviteTest.response); cmp != "" { + t.Logf("TestId `%d` `%s` URL `%s` body `%s`", idx, inviteTest.desc, inviteTest.url, cmp) + t.Fail() } } } diff --git a/api/signup_test.go b/api/signup_test.go index 7222eeeaa..6d86266b2 100644 --- a/api/signup_test.go +++ b/api/signup_test.go @@ -73,7 +73,7 @@ func TestSignupResponds(t *testing.T) { method: "PUT", url: "/accept/signup/WithoutPassword", respCode: 409, - response: jo{ + response: testJSONObject{ "code": float64(409), "error": float64(1001), "reason": "User does not have a password", @@ -83,11 +83,11 @@ func TestSignupResponds(t *testing.T) { // failure - user does not yet have a password; password missing, but birthday correct method: "PUT", url: "/accept/signup/WithoutPassword", - body: jo{ + body: testJSONObject{ "birthday": "2016-01-01", }, respCode: 409, - response: jo{ + response: testJSONObject{ "code": float64(409), "error": float64(1002), "reason": "Password is missing", @@ -97,12 +97,12 @@ func TestSignupResponds(t *testing.T) { // failure - user does not yet have a password; password invalid, but birthday correct method: "PUT", url: "/accept/signup/WithoutPassword", - body: jo{ + body: testJSONObject{ "password": "1234", "birthday": "2016-01-01", }, respCode: 409, - response: jo{ + response: testJSONObject{ "code": float64(409), "error": float64(1003), "reason": "Password specified is invalid", @@ -112,11 +112,11 @@ func TestSignupResponds(t *testing.T) { // failure - user does not yet have a password; password valid and birthday missing method: "PUT", url: "/accept/signup/WithoutPassword", - body: jo{ + body: testJSONObject{ "password": "12345678", }, respCode: 409, - response: jo{ + response: testJSONObject{ "code": float64(409), "error": float64(1004), "reason": "Birthday is missing", @@ -126,12 +126,12 @@ func TestSignupResponds(t *testing.T) { // failure - user does not yet have a password; password valid and birthday invalid method: "PUT", url: "/accept/signup/WithoutPassword", - body: jo{ + body: testJSONObject{ "password": "12345678", "birthday": "aaaaaaaa", }, respCode: 409, - response: jo{ + response: testJSONObject{ "code": float64(409), "error": float64(1005), "reason": "Birthday specified is invalid", @@ -141,12 +141,12 @@ func TestSignupResponds(t *testing.T) { // failure - user does not yet have a password; password valid and birthday not correct method: "PUT", url: "/accept/signup/WithoutPassword", - body: jo{ + body: testJSONObject{ "password": "12345678", "birthday": "2015-12-31", }, respCode: 409, - response: jo{ + response: testJSONObject{ "code": float64(409), "error": float64(1006), "reason": "Birthday specified does not match patient birthday", @@ -156,7 +156,7 @@ func TestSignupResponds(t *testing.T) { // all good - user does not yet have a password; password valid and birthday correct method: "PUT", url: "/accept/signup/WithoutPassword", - body: jo{ + body: testJSONObject{ "password": "12345678", "birthday": "2016-01-01", }, @@ -172,7 +172,7 @@ func TestSignupResponds(t *testing.T) { method: "PUT", url: "/dismiss/signup/UID", respCode: 200, - body: jo{ + body: testJSONObject{ "key": "1234-xXd", }, }, @@ -186,7 +186,7 @@ func TestSignupResponds(t *testing.T) { method: "PUT", url: "/signup/UID", respCode: 200, - body: jo{ + body: testJSONObject{ "key": "1234-xXd", }, }, @@ -253,7 +253,7 @@ func TestSignupResponds(t *testing.T) { if response.Body.Len() != 0 && len(test.response) != 0 { // compare bodies by comparing the unmarshalled JSON results - var result = &jo{} + var result = &testJSONObject{} if err := json.NewDecoder(response.Body).Decode(result); err != nil { t.Logf("Err decoding nonempty response body: [%v]\n [%v]\n", err, response.Body) diff --git a/models/confirmation.go b/models/confirmation.go index 96400ee22..b401c37f8 100644 --- a/models/confirmation.go +++ b/models/confirmation.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/base64" "encoding/json" + "fmt" "log" "time" ) @@ -130,6 +131,46 @@ func (c *Confirmation) UpdateStatus(newStatus Status) { c.Modified = time.Now() } +func (c *Confirmation) ValidateCreatorID(expectedCreatorID string, validationErrors *[]error) *Confirmation { + if expectedCreatorID != c.CreatorId { + *validationErrors = append( + *validationErrors, + fmt.Errorf("Confirmation expected CreatorID `%s` but had `%s`", expectedCreatorID, c.CreatorId), + ) + } + return c +} + +func (c *Confirmation) ValidateUserID(expectedUserID string, validationErrors *[]error) *Confirmation { + if expectedUserID != c.UserId { + *validationErrors = append( + *validationErrors, + fmt.Errorf("Confirmation expected UserID of `%s` but had `%s`", expectedUserID, c.UserId), + ) + } + return c +} + +func (c *Confirmation) ValidateStatus(expectedStatus Status, validationErrors *[]error) *Confirmation { + if expectedStatus != c.Status { + *validationErrors = append( + *validationErrors, + fmt.Errorf("Confirmation expected Status of `%s` but had `%s`", expectedStatus, c.Status), + ) + } + return c +} + +func (c *Confirmation) ValidateType(expectedType Type, validationErrors *[]error) *Confirmation { + if expectedType != c.Type { + *validationErrors = append( + *validationErrors, + fmt.Errorf("Confirmation expected Type `%s` but had `%s`", expectedType, c.Type), + ) + } + return c +} + func (c *Confirmation) IsExpired() bool { timeout, ok := Timeouts[c.Type] if !ok {