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  }