github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/it/impl_invite_test.go (about) 1 /* 2 * Copyright (c) 2023-present unTill Pro, Ltd. 3 */ 4 5 package sys_it 6 7 import ( 8 "fmt" 9 "log" 10 "strings" 11 "testing" 12 13 "github.com/stretchr/testify/require" 14 "github.com/voedger/voedger/pkg/istructs" 15 "github.com/voedger/voedger/pkg/state/smtptest" 16 "github.com/voedger/voedger/pkg/sys/invite" 17 coreutils "github.com/voedger/voedger/pkg/utils" 18 it "github.com/voedger/voedger/pkg/vit" 19 ) 20 21 var ( 22 initialRoles = "initial.Roles" 23 newRoles = "new.Roles" 24 inviteEmailTemplate = "text:" + strings.Join([]string{ 25 invite.EmailTemplatePlaceholder_VerificationCode, 26 invite.EmailTemplatePlaceholder_InviteID, 27 invite.EmailTemplatePlaceholder_WSID, 28 invite.EmailTemplatePlaceholder_WSName, 29 invite.EmailTemplatePlaceholder_Email, 30 }, ";") 31 inviteEmailSubject = "you are invited" 32 ) 33 34 // impossible to use the test workspace again with the same login due of invite error `subject already exists` 35 func TestInvite_BasicUsage(t *testing.T) { 36 require := require.New(t) 37 vit := it.NewVIT(t, &it.SharedConfig_App1) 38 defer vit.TearDown() 39 wsName := "TestInvite_BasicUsage_ws" 40 wsParams := it.SimpleWSParams(wsName) 41 updateRolesEmailTemplate := "text:" + invite.EmailTemplatePlaceholder_Roles 42 updateRolesEmailSubject := "your roles are updated" 43 expireDatetime := vit.Now().UnixMilli() 44 updatedRoles := "updated.Roles" 45 46 email1 := fmt.Sprintf("testinvite_basicusage_%d@123.com", vit.NextNumber()) 47 email2 := fmt.Sprintf("testinvite_basicusage_%d@123.com", vit.NextNumber()) 48 email3 := fmt.Sprintf("testinvite_basicusage_%d@123.com", vit.NextNumber()) 49 login1 := vit.SignUp(email1, "1", istructs.AppQName_test1_app1) 50 login2 := vit.SignUp(email2, "1", istructs.AppQName_test1_app1) 51 login1Prn := vit.SignIn(login1) 52 login2Prn := vit.SignIn(login2) 53 prn := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail) 54 ws := vit.CreateWorkspace(wsParams, prn) 55 56 initiateUpdateInviteRoles := func(inviteID int64) { 57 vit.PostWS(ws, "c.sys.InitiateUpdateInviteRoles", fmt.Sprintf(`{"args":{"InviteID":%d,"Roles":"%s","EmailTemplate":"%s","EmailSubject":"%s"}}`, inviteID, updatedRoles, updateRolesEmailTemplate, updateRolesEmailSubject)) 58 } 59 60 findCDocInviteByID := func(inviteID int64) []interface{} { 61 return vit.PostWS(ws, "q.sys.Collection", fmt.Sprintf(` 62 {"args":{"Schema":"sys.Invite"}, 63 "elements":[{"fields":[ 64 "SubjectKind", 65 "Login", 66 "Email", 67 "Roles", 68 "ExpireDatetime", 69 "VerificationCode", 70 "State", 71 "Created", 72 "Updated", 73 "SubjectID", 74 "InviteeProfileWSID", 75 "ActualLogin", 76 "sys.ID" 77 ]}], 78 "filters":[{"expr":"eq","args":{"field":"sys.ID","value":%d}}]}`, inviteID)).SectionRow(0) 79 } 80 81 findCDocSubjectByLogin := func(login string) []interface{} { 82 return vit.PostWS(ws, "q.sys.Collection", fmt.Sprintf(` 83 {"args":{"Schema":"sys.Subject"}, 84 "elements":[{"fields":[ 85 "Login", 86 "SubjectKind", 87 "Roles", 88 "sys.ID", 89 "sys.IsActive" 90 ]}], 91 "filters":[{"expr":"eq","args":{"field":"Login","value":"%s"}}]}`, login)).SectionRow(0) 92 } 93 94 //Invite existing users 95 inviteID := InitiateInvitationByEMail(vit, ws, expireDatetime, email1, initialRoles, inviteEmailTemplate, inviteEmailSubject) 96 inviteID2 := InitiateInvitationByEMail(vit, ws, expireDatetime, email2, initialRoles, inviteEmailTemplate, inviteEmailSubject) 97 inviteID3 := InitiateInvitationByEMail(vit, ws, expireDatetime, email3, initialRoles, inviteEmailTemplate, inviteEmailSubject) 98 99 // need to gather email first because 100 actualEmails := []smtptest.Message{vit.CaptureEmail(), vit.CaptureEmail(), vit.CaptureEmail()} 101 102 WaitForInviteState(vit, ws, inviteID, invite.State_ToBeInvited, invite.State_Invited) 103 WaitForInviteState(vit, ws, inviteID2, invite.State_ToBeInvited, invite.State_Invited) 104 WaitForInviteState(vit, ws, inviteID3, invite.State_ToBeInvited, invite.State_Invited) 105 106 cDocInvite := findCDocInviteByID(inviteID) 107 108 require.Equal(email1, cDocInvite[1]) 109 require.Equal(email1, cDocInvite[2]) 110 require.Equal(initialRoles, cDocInvite[3]) 111 require.Equal(float64(expireDatetime), cDocInvite[4]) 112 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[7]) 113 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[8]) 114 115 //Check that emails were sent 116 var verificationCodeEmail, verificationCodeEmail2, verificationCodeEmail3 string 117 for _, actualEmail := range actualEmails { 118 switch actualEmail.To[0] { 119 case email1: 120 verificationCodeEmail = actualEmail.Body[:6] 121 case email2: 122 verificationCodeEmail2 = actualEmail.Body[:6] 123 case email3: 124 verificationCodeEmail3 = actualEmail.Body[:6] 125 } 126 } 127 expectedEmails := []smtptest.Message{ 128 { 129 Subject: inviteEmailSubject, 130 From: it.TestSMTPCfg.GetFrom(), 131 To: []string{email1}, 132 Body: fmt.Sprintf("%s;%d;%d;%s;%s", verificationCodeEmail, inviteID, ws.WSID, wsName, email1), 133 CC: []string{}, 134 BCC: []string{}, 135 }, 136 { 137 Subject: inviteEmailSubject, 138 From: it.TestSMTPCfg.GetFrom(), 139 To: []string{email2}, 140 Body: fmt.Sprintf("%s;%d;%d;%s;%s", verificationCodeEmail2, inviteID2, ws.WSID, wsName, email2), 141 CC: []string{}, 142 BCC: []string{}, 143 }, 144 { 145 Subject: inviteEmailSubject, 146 From: it.TestSMTPCfg.GetFrom(), 147 To: []string{email3}, 148 Body: fmt.Sprintf("%s;%d;%d;%s;%s", verificationCodeEmail3, inviteID3, ws.WSID, wsName, email3), 149 CC: []string{}, 150 BCC: []string{}, 151 }, 152 } 153 require.EqualValues(expectedEmails, actualEmails) 154 155 cDocInvite = findCDocInviteByID(inviteID2) 156 157 require.Equal(verificationCodeEmail2, cDocInvite[5]) 158 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[8]) 159 160 // overwrite roles is possible when the invite is not accepted yet 161 verificationCodeEmail = testOverwriteRoles(t, vit, ws, email1, inviteID) 162 163 //Cancel then invite it again (inviteID3) 164 vit.PostWS(ws, "c.sys.CancelSentInvite", fmt.Sprintf(`{"args":{"InviteID":%d}}`, inviteID3)) 165 WaitForInviteState(vit, ws, inviteID3, invite.State_ToBeCancelled, invite.State_Cancelled) 166 InitiateInvitationByEMail(vit, ws, expireDatetime, email3, initialRoles, inviteEmailTemplate, inviteEmailSubject) 167 _ = vit.CaptureEmail() 168 WaitForInviteState(vit, ws, inviteID3, invite.State_ToBeInvited, invite.State_Invited) 169 170 //Join workspaces 171 InitiateJoinWorkspace(vit, ws, inviteID, login1Prn, verificationCodeEmail) 172 InitiateJoinWorkspace(vit, ws, inviteID2, login2Prn, verificationCodeEmail2) 173 174 // State_ToBeJoined will be set for a very short period of time so let's do not catch it 175 WaitForInviteState(vit, ws, inviteID, invite.State_ToBeJoined, invite.State_Joined) 176 WaitForInviteState(vit, ws, inviteID2, invite.State_ToBeJoined, invite.State_Joined) 177 178 cDocInvite = findCDocInviteByID(inviteID2) 179 180 require.Equal(float64(login2Prn.ProfileWSID), cDocInvite[10]) 181 require.Equal(float64(istructs.SubjectKind_User), cDocInvite[0]) 182 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[8]) 183 184 cDocJoinedWorkspace := FindCDocJoinedWorkspaceByInvitingWorkspaceWSIDAndLogin(vit, ws.WSID, login2Prn) 185 186 require.Equal(initialRoles, cDocJoinedWorkspace.roles) 187 require.Equal(wsName, cDocJoinedWorkspace.wsName) 188 189 cDocSubject := findCDocSubjectByLogin(email1) 190 191 require.Equal(email1, cDocSubject[0]) 192 require.Equal(float64(istructs.SubjectKind_User), cDocSubject[1]) 193 require.Equal(newRoles, cDocSubject[2]) // overwritten 194 195 t.Run("reinivite the joined already -> error", func(t *testing.T) { 196 body := fmt.Sprintf(`{"args":{"Email":"%s","Roles":"%s","ExpireDatetime":%d,"EmailTemplate":"%s","EmailSubject":"%s"}}`, 197 email1, initialRoles, 1674751138000, inviteEmailTemplate, inviteEmailSubject) 198 vit.PostWS(ws, "c.sys.InitiateInvitationByEMail", body, coreutils.Expect400(invite.ErrSubjectAlreadyExists.Error())) 199 }) 200 201 //Update roles 202 initiateUpdateInviteRoles(inviteID) 203 initiateUpdateInviteRoles(inviteID2) 204 205 //Check that emails were sent 206 require.Equal(updatedRoles, vit.CaptureEmail().Body) 207 message := vit.CaptureEmail() 208 require.Equal(updateRolesEmailSubject, message.Subject) 209 require.Equal(it.TestSMTPCfg.GetFrom(), message.From) 210 require.Equal([]string{email2}, message.To) 211 require.Equal(updatedRoles, message.Body) 212 213 WaitForInviteState(vit, ws, inviteID, invite.State_Joined, invite.State_ToUpdateRoles, invite.State_Joined) 214 WaitForInviteState(vit, ws, inviteID2, invite.State_Joined, invite.State_ToUpdateRoles, invite.State_Joined) 215 cDocInvite = findCDocInviteByID(inviteID) 216 217 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[8]) 218 219 cDocSubject = findCDocSubjectByLogin(email2) 220 221 require.Equal(updatedRoles, cDocSubject[2]) 222 223 //TODO Denis how to get WS by login? I want to check sys.JoinedWorkspace 224 225 WaitForInviteState(vit, ws, inviteID, invite.State_ToBeJoined, invite.State_Joined) 226 WaitForInviteState(vit, ws, inviteID2, invite.State_ToBeJoined, invite.State_Joined) 227 228 //Cancel accepted invite 229 vit.PostWS(ws, "c.sys.InitiateCancelAcceptedInvite", fmt.Sprintf(`{"args":{"InviteID":%d}}`, inviteID)) 230 231 // State_ToBeCancelled will be set for a veri short period of time so let's do not catch it 232 WaitForInviteState(vit, ws, inviteID, invite.State_ToBeCancelled, invite.State_Cancelled) 233 234 cDocInvite = findCDocInviteByID(inviteID) 235 236 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[8]) 237 238 cDocSubject = findCDocSubjectByLogin(email1) 239 240 require.False(cDocSubject[4].(bool)) 241 242 cDocInvite = findCDocInviteByID(inviteID) 243 244 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[8]) 245 246 //Leave workspace 247 vit.PostWS(ws, "c.sys.InitiateLeaveWorkspace", "{}", coreutils.WithAuthorizeBy(login2Prn.Token)) 248 249 WaitForInviteState(vit, ws, inviteID2, invite.State_ToBeLeft, invite.State_Left) 250 251 cDocInvite = findCDocInviteByID(inviteID2) 252 253 require.Equal(float64(vit.Now().UnixMilli()), cDocInvite[8]) 254 255 cDocSubject = findCDocSubjectByLogin(email2) 256 257 require.False(cDocSubject[4].(bool)) 258 259 //TODO check InviteeProfile joined workspace 260 261 //Re-invite 262 newRoles := "new.roles" 263 InitiateInvitationByEMail(vit, ws, expireDatetime, email2, newRoles, inviteEmailTemplate, inviteEmailSubject) 264 log.Println(vit.CaptureEmail().Body) 265 WaitForInviteState(vit, ws, inviteID2, invite.State_Left, invite.State_Invited) 266 cDocInvite = findCDocInviteByID(inviteID2) 267 require.Equal(newRoles, cDocInvite[3].(string)) 268 } 269 270 func TestCancelSentInvite(t *testing.T) { 271 vit := it.NewVIT(t, &it.SharedConfig_App1) 272 defer vit.TearDown() 273 274 email := fmt.Sprintf("testcancelsentinvite_%d@123.com", vit.NextNumber()) 275 login := vit.SignUp(email, "1", istructs.AppQName_test1_app1) 276 loginPrn := vit.SignIn(login) 277 wsParams := it.SimpleWSParams("TestCancelSentInvite_ws") 278 ws := vit.CreateWorkspace(wsParams, loginPrn) 279 280 t.Run("basic usage", func(t *testing.T) { 281 inviteID := InitiateInvitationByEMail(vit, ws, 1674751138000, email, initialRoles, inviteEmailTemplate, inviteEmailSubject) 282 WaitForInviteState(vit, ws, inviteID, invite.State_ToBeInvited, invite.State_Invited) 283 284 //Read it for successful vit tear down 285 vit.CaptureEmail() 286 287 vit.PostWS(ws, "c.sys.CancelSentInvite", fmt.Sprintf(`{"args":{"InviteID":%d}}`, inviteID)) 288 WaitForInviteState(vit, ws, inviteID, invite.State_ToBeCancelled, invite.State_Cancelled) 289 }) 290 t.Run("invite not exists -> 400 bad request", func(t *testing.T) { 291 vit.PostWS(ws, "c.sys.CancelSentInvite", fmt.Sprintf(`{"args":{"InviteID":%d}}`, istructs.NonExistingRecordID), coreutils.Expect400RefIntegrity_Existence()) 292 }) 293 } 294 295 func testOverwriteRoles(t *testing.T, vit *it.VIT, ws *it.AppWorkspace, email string, inviteID int64) (verificationCode string) { 296 require := require.New(t) 297 298 // reinvite when invitation is not accepted yet -> roles must be overwritten 299 newInviteID := InitiateInvitationByEMail(vit, ws, 1674751138000, email, newRoles, inviteEmailTemplate, inviteEmailSubject) 300 require.Zero(newInviteID) 301 WaitForInviteState(vit, ws, inviteID, invite.State_ToBeInvited, invite.State_Invited) 302 actualEmail := vit.CaptureEmail() 303 verificationCode = actualEmail.Body[:6] 304 305 // expect roles are overwritten in cdoc.sys.Invite 306 body := fmt.Sprintf(`{"args":{"Schema":"sys.Invite","ID":%d},"elements":[{"fields":["Roles"]}]}`, inviteID) 307 resp := vit.PostWS(ws, "q.sys.Collection", body) 308 require.Equal(newRoles, resp.SectionRow()[0].(string)) 309 310 return verificationCode 311 }