feat(server): delay subscription after invitation accepted or approved (#11992)

This commit is contained in:
liuyi
2025-04-30 14:27:47 +08:00
committed by GitHub
parent 9a721c65b5
commit 2e1bed061e
49 changed files with 1990 additions and 2694 deletions
@@ -13,6 +13,7 @@ import {
AFFiNELogger,
CacheInterceptor,
CloudThrottlerGuard,
EventBus,
GlobalExceptionFilter,
JobQueue,
OneMB,
@@ -20,6 +21,7 @@ import {
import { SocketIoAdapter } from '../../base/websocket';
import { AuthGuard, AuthService } from '../../core/auth';
import { Mailer } from '../../core/mail';
import { Models } from '../../models';
import {
createFactory,
MockedUser,
@@ -43,6 +45,8 @@ export class TestingApp extends NestApplication {
create = createFactory(this.get(PrismaClient, { strict: false }));
mails = this.get(Mailer, { strict: false }) as MockMailer;
queue = this.get(JobQueue, { strict: false }) as MockJobQueue;
eventBus = this.get(EventBus, { strict: false });
models = this.get(Models, { strict: false });
get url() {
const server = this.getHttpServer();
@@ -1,456 +0,0 @@
import {
acceptInviteByInviteIdMutation,
createInviteLinkMutation,
getInviteInfoQuery,
getMembersByWorkspaceIdQuery,
inviteByEmailMutation,
leaveWorkspaceMutation,
revokeMemberPermissionMutation,
WorkspaceInviteLinkExpireTime,
WorkspaceMemberStatus,
} from '@affine/graphql';
import { faker } from '@faker-js/faker';
import { Models } from '../../../models';
import { Mockers } from '../../mocks';
import { app, e2e } from '../test';
e2e('should invite a user', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
const result = await app.gql({
query: inviteByEmailMutation,
variables: {
email: u2.email,
workspaceId: workspace.id,
},
});
t.truthy(result, 'failed to invite user');
// add invitation notification job
const invitationNotification = app.queue.last('notification.sendInvitation');
t.is(invitationNotification.payload.inviterId, owner.id);
t.is(invitationNotification.payload.inviteId, result.invite);
// invitation status is pending
const { getInviteInfo } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId: result.invite,
},
});
t.is(getInviteInfo.status, WorkspaceMemberStatus.Pending);
// u2 accept invite
await app.switchUser(u2);
await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId: result.invite,
},
});
// invitation status is accepted
const { getInviteInfo: getInviteInfo2 } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId: result.invite,
},
});
t.is(getInviteInfo2.status, WorkspaceMemberStatus.Accepted);
});
e2e('should leave a workspace', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u2.id,
});
await app.switchUser(u2.id);
const { leaveWorkspace } = await app.gql({
query: leaveWorkspaceMutation,
variables: {
workspaceId: workspace.id,
},
});
t.true(leaveWorkspace, 'failed to leave workspace');
});
e2e('should revoke a user', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u2.id,
});
const { revoke } = await app.gql({
query: revokeMemberPermissionMutation,
variables: {
workspaceId: workspace.id,
userId: u2.id,
},
});
t.true(revoke, 'failed to revoke user');
});
e2e('should revoke a user on under review', async t => {
const user = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: user.id,
status: WorkspaceMemberStatus.UnderReview,
});
const { revoke } = await app.gql({
query: revokeMemberPermissionMutation,
variables: {
workspaceId: workspace.id,
userId: user.id,
},
});
t.true(revoke, 'failed to revoke user');
const requestDeclinedNotification = app.queue.last(
'notification.sendInvitationReviewDeclined'
);
t.truthy(requestDeclinedNotification);
t.deepEqual(
requestDeclinedNotification.payload,
{
userId: user.id,
workspaceId: workspace.id,
reviewerId: owner.id,
},
'should send review declined notification'
);
});
e2e('should create user if not exist', async t => {
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
const email = faker.internet.email();
await app.gql({
query: inviteByEmailMutation,
variables: {
email,
workspaceId: workspace.id,
},
});
const u2 = await app.get(Models).user.getUserByEmail(email);
t.truthy(u2, 'failed to create user');
});
e2e('should invite a user by link', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
const invite1 = await app.gql({
query: inviteByEmailMutation,
variables: {
email: u2.email,
workspaceId: workspace.id,
},
});
await app.switchUser(u2);
const accept = await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
inviteId: invite1.invite,
workspaceId: workspace.id,
},
});
t.true(accept.acceptInviteById, 'failed to accept invite');
await app.switchUser(owner);
const invite2 = await app.gql({
query: inviteByEmailMutation,
variables: {
email: u2.email,
workspaceId: workspace.id,
},
});
t.is(
invite2.invite,
invite1.invite,
'repeat the invitation must return same id'
);
const member = await app
.get(Models)
.workspaceUser.getActive(workspace.id, u2.id);
t.truthy(member, 'failed to invite user');
t.is(member!.id, invite1.invite, 'failed to check invite id');
});
e2e('should send invitation notification and leave email', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
const invite = await app.gql({
query: inviteByEmailMutation,
variables: {
email: u2.email,
workspaceId: workspace.id,
},
});
const invitationNotification = app.queue.last('notification.sendInvitation');
t.is(invitationNotification.payload.inviterId, owner.id);
t.is(invitationNotification.payload.inviteId, invite.invite);
await app.switchUser(u2);
const accept = await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
inviteId: invite.invite,
workspaceId: workspace.id,
},
});
t.true(accept.acceptInviteById, 'failed to accept invite');
const acceptedNotification = app.queue.last(
'notification.sendInvitationAccepted'
);
t.is(acceptedNotification.payload.inviterId, owner.id);
t.is(acceptedNotification.payload.inviteId, invite.invite);
const leave = await app.gql({
query: leaveWorkspaceMutation,
variables: {
workspaceId: workspace.id,
sendLeaveMail: true,
},
});
t.true(leave.leaveWorkspace, 'failed to leave workspace');
const leaveMail = app.mails.last('MemberLeave');
t.is(leaveMail.to, owner.email);
t.is(leaveMail.props.user.$$userId, u2.id);
});
e2e('should support pagination for member', async t => {
const u1 = await app.signup();
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u1.id,
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u2.id,
});
let result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 0,
take: 2,
},
});
t.is(result.workspace.memberCount, 3);
t.is(result.workspace.members.length, 2);
result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 2,
take: 2,
},
});
t.is(result.workspace.memberCount, 3);
t.is(result.workspace.members.length, 1);
result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 3,
take: 2,
},
});
t.is(result.workspace.memberCount, 3);
t.is(result.workspace.members.length, 0);
});
e2e('should limit member count correctly', async t => {
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await Promise.all(
Array.from({ length: 10 }).map(async () => {
const user = await app.signup();
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: user.id,
});
})
);
await app.switchUser(owner);
const result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 0,
take: 10,
},
});
t.is(result.workspace.memberCount, 11);
t.is(result.workspace.members.length, 10);
});
e2e('should get invite link info with status', async t => {
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.login(owner);
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteId = link.split('/').pop()!;
// owner/member see accept status
const { getInviteInfo } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId,
},
});
t.truthy(getInviteInfo, 'failed to get invite info');
t.is(getInviteInfo.status, WorkspaceMemberStatus.Accepted);
// non-member see null status
await app.signup();
const { getInviteInfo: getInviteInfo2 } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId,
},
});
t.truthy(getInviteInfo2, 'failed to get invite info');
t.is(getInviteInfo2.status, null);
// pending-member see under review status
await app.signup();
await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId,
},
});
const { getInviteInfo: getInviteInfo3 } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId,
},
});
t.truthy(getInviteInfo3, 'failed to get invite info');
t.is(getInviteInfo3.status, WorkspaceMemberStatus.UnderReview);
});
e2e(
'should accept invitation by link directly if status is pending',
async t => {
const owner = await app.create(Mockers.User);
const member = await app.create(Mockers.User);
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.login(owner);
// create a pending invitation
const invite = await app.gql({
query: inviteByEmailMutation,
variables: {
email: member.email,
workspaceId: workspace.id,
},
});
t.truthy(invite, 'failed to create invitation');
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteLinkId = link.split('/').pop()!;
// member accept invitation by link
await app.login(member);
await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
inviteId: inviteLinkId,
workspaceId: workspace.id,
},
});
const { getInviteInfo } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId: invite.invite,
},
});
t.is(getInviteInfo.status, WorkspaceMemberStatus.Accepted);
}
);
@@ -1,9 +1,459 @@
import { getMembersByWorkspaceIdQuery } from '@affine/graphql';
import {
acceptInviteByInviteIdMutation,
createInviteLinkMutation,
getInviteInfoQuery,
getMembersByWorkspaceIdQuery,
inviteByEmailsMutation,
leaveWorkspaceMutation,
revokeMemberPermissionMutation,
WorkspaceInviteLinkExpireTime,
WorkspaceMemberStatus,
} from '@affine/graphql';
import { faker } from '@faker-js/faker';
import { Models } from '../../../models';
import { Mockers } from '../../mocks';
import { app, e2e } from '../test';
async function createTeamWorkspace(quantity = 10) {
const owner = await app.create(Mockers.User);
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.TeamWorkspace, {
id: workspace.id,
quantity,
});
return {
owner,
workspace,
};
}
e2e('should invite a user', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
const result = await app.gql({
query: inviteByEmailsMutation,
variables: {
emails: [u2.email],
workspaceId: workspace.id,
},
});
t.truthy(result, 'failed to invite user');
// add invitation notification job
const invitationNotification = await app.queue.waitFor(
'notification.sendInvitation'
);
t.is(invitationNotification.payload.inviterId, owner.id);
t.is(
invitationNotification.payload.inviteId,
result.inviteMembers[0].inviteId!
);
// invitation status is pending
const { getInviteInfo } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId: invitationNotification.payload.inviteId,
},
});
t.is(getInviteInfo.status, WorkspaceMemberStatus.Pending);
// u2 accept invite
await app.switchUser(u2);
await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId: invitationNotification.payload.inviteId,
},
});
// invitation status is accepted
const { getInviteInfo: getInviteInfo2 } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId: invitationNotification.payload.inviteId,
},
});
t.is(getInviteInfo2.status, WorkspaceMemberStatus.Accepted);
});
e2e('should leave a workspace', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u2.id,
});
await app.switchUser(u2.id);
const { leaveWorkspace } = await app.gql({
query: leaveWorkspaceMutation,
variables: {
workspaceId: workspace.id,
},
});
t.true(leaveWorkspace, 'failed to leave workspace');
const leaveMail = await app.mails.waitFor('MemberLeave');
t.is(leaveMail.to, owner.email);
t.is(leaveMail.props.user.$$userId, u2.id);
});
e2e('should revoke a user', async t => {
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u2.id,
});
const { revokeMember } = await app.gql({
query: revokeMemberPermissionMutation,
variables: {
workspaceId: workspace.id,
userId: u2.id,
},
});
t.true(revokeMember, 'failed to revoke user');
});
e2e('should revoke a user on under review', async t => {
const user = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: user.id,
status: WorkspaceMemberStatus.UnderReview,
});
const { revokeMember } = await app.gql({
query: revokeMemberPermissionMutation,
variables: {
workspaceId: workspace.id,
userId: user.id,
},
});
t.true(revokeMember, 'failed to revoke user');
const requestDeclinedNotification = app.queue.last(
'notification.sendInvitationReviewDeclined'
);
t.truthy(requestDeclinedNotification);
t.deepEqual(
requestDeclinedNotification.payload,
{
userId: user.id,
workspaceId: workspace.id,
reviewerId: owner.id,
},
'should send review declined notification'
);
});
e2e('should create user if not exist', async t => {
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
const email = faker.internet.email();
await app.gql({
query: inviteByEmailsMutation,
variables: {
emails: [email],
workspaceId: workspace.id,
},
});
const u2 = await app.get(Models).user.getUserByEmail(email);
t.truthy(u2, 'failed to create user');
});
e2e('should support pagination for member', async t => {
const u1 = await app.signup();
const u2 = await app.signup();
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u1.id,
});
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: u2.id,
});
let result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 0,
take: 2,
},
});
t.is(result.workspace.memberCount, 3);
t.is(result.workspace.members.length, 2);
result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 2,
take: 2,
},
});
t.is(result.workspace.memberCount, 3);
t.is(result.workspace.members.length, 1);
result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 3,
take: 2,
},
});
t.is(result.workspace.memberCount, 3);
t.is(result.workspace.members.length, 0);
});
e2e('should limit member count correctly', async t => {
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await Promise.all(
Array.from({ length: 10 }).map(async () => {
const user = await app.signup();
await app.create(Mockers.WorkspaceUser, {
workspaceId: workspace.id,
userId: user.id,
});
})
);
await app.switchUser(owner);
const result = await app.gql({
query: getMembersByWorkspaceIdQuery,
variables: {
workspaceId: workspace.id,
skip: 0,
take: 10,
},
});
t.is(result.workspace.memberCount, 11);
t.is(result.workspace.members.length, 10);
});
e2e('should get invite link info with status', async t => {
const owner = await app.signup();
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.login(owner);
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteId = link.split('/').pop()!;
// owner/member see accept status
const { getInviteInfo } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId,
},
});
t.truthy(getInviteInfo, 'failed to get invite info');
t.is(getInviteInfo.status, WorkspaceMemberStatus.Accepted);
// non-member see null status
await app.signup();
const { getInviteInfo: getInviteInfo2 } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId,
},
});
t.truthy(getInviteInfo2, 'failed to get invite info');
t.is(getInviteInfo2.status, null);
// pending-member see under review status
await app.signup();
await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId,
},
});
const { getInviteInfo: getInviteInfo3 } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId,
},
});
t.truthy(getInviteInfo3, 'failed to get invite info');
t.is(getInviteInfo3.status, WorkspaceMemberStatus.UnderReview);
});
e2e(
'should accept invitation by link directly if status is pending',
async t => {
const owner = await app.create(Mockers.User);
const member = await app.create(Mockers.User);
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
});
await app.login(owner);
// create a pending invitation
const invite = await app.gql({
query: inviteByEmailsMutation,
variables: {
emails: [member.email],
workspaceId: workspace.id,
},
});
t.truthy(invite, 'failed to create invitation');
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteLinkId = link.split('/').pop()!;
// member accept invitation by link
await app.login(member);
await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
inviteId: inviteLinkId,
workspaceId: workspace.id,
},
});
const { getInviteInfo } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId: invite.inviteMembers[0].inviteId!,
},
});
t.is(getInviteInfo.status, WorkspaceMemberStatus.Accepted);
}
);
e2e(
'should invite by link and send review request notification below quota limit',
async t => {
const { owner, workspace } = await createTeamWorkspace();
await app.login(owner);
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteId = link.split('/').pop()!;
// accept invite by link
await app.signup();
const result = await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId,
},
});
t.truthy(result, 'failed to accept invite');
const notification = app.queue.last(
'notification.sendInvitationReviewRequest'
);
t.is(notification.payload.reviewerId, owner.id);
t.truthy(notification.payload.inviteId);
}
);
e2e(
'should invite by link and send review request notification over quota limit',
async t => {
const { owner, workspace } = await createTeamWorkspace(1);
await app.login(owner);
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteId = link.split('/').pop()!;
// accept invite by link
await app.signup();
const result = await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId,
},
});
t.truthy(result, 'failed to accept invite');
const notification = app.queue.last(
'notification.sendInvitationReviewRequest'
);
t.is(notification.payload.reviewerId, owner.id);
t.truthy(notification.payload.inviteId);
}
);
e2e(
'should search members by name and email support case insensitive',
async t => {
@@ -1,146 +1,167 @@
import {
acceptInviteByInviteIdMutation,
createInviteLinkMutation,
getInviteInfoQuery,
inviteByEmailMutation,
WorkspaceInviteLinkExpireTime,
inviteByEmailsMutation,
WorkspaceMemberStatus,
} from '@affine/graphql';
import { WorkspaceRole } from '../../../models';
import { Mockers } from '../../mocks';
import { app, e2e } from '../test';
async function createTeamWorkspace(quantity = 10) {
const createTeamWorkspace = async (memberLimit = 3) => {
const owner = await app.create(Mockers.User);
const workspace = await app.create(Mockers.Workspace, {
owner: { id: owner.id },
owner: {
id: owner.id,
},
});
await app.create(Mockers.TeamWorkspace, {
id: workspace.id,
quantity,
quantity: memberLimit,
});
const writer = await app.create(Mockers.User);
await app.create(Mockers.WorkspaceUser, {
userId: writer.id,
workspaceId: workspace.id,
});
const admin = await app.create(Mockers.User);
await app.create(Mockers.WorkspaceUser, {
userId: admin.id,
workspaceId: workspace.id,
type: WorkspaceRole.Admin,
});
const external = await app.create(Mockers.User);
return {
owner,
workspace,
owner,
admin,
writer,
external,
};
}
};
e2e(
'should invite by link and send review request notification below quota limit',
async t => {
const { owner, workspace } = await createTeamWorkspace();
const getInvitationInfo = async (inviteId: string) => {
const result = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId,
},
});
return result.getInviteInfo;
};
await app.login(owner);
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteId = link.split('/').pop()!;
e2e('should set new invited users to AllocatingSeat', async t => {
const { owner, workspace } = await createTeamWorkspace();
await app.login(owner);
// accept invite by link
await app.signup();
const result = await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId,
},
});
t.truthy(result, 'failed to accept invite');
const notification = app.queue.last(
'notification.sendInvitationReviewRequest'
);
t.is(notification.payload.reviewerId, owner.id);
t.truthy(notification.payload.inviteId);
}
);
const u1 = await app.createUser();
e2e(
'should invite by link and send review request notification over quota limit',
async t => {
const { owner, workspace } = await createTeamWorkspace(1);
const result = await app.gql({
query: inviteByEmailsMutation,
variables: {
workspaceId: workspace.id,
emails: [u1.email],
},
});
await app.login(owner);
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteId = link.split('/').pop()!;
t.not(result.inviteMembers[0].inviteId, null);
// accept invite by link
await app.signup();
const result = await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
workspaceId: workspace.id,
inviteId,
},
});
t.truthy(result, 'failed to accept invite');
const notification = app.queue.last(
'notification.sendInvitationReviewRequest'
);
t.is(notification.payload.reviewerId, owner.id);
t.truthy(notification.payload.inviteId);
}
);
const invitationInfo = await getInvitationInfo(
result.inviteMembers[0].inviteId!
);
t.is(invitationInfo.status, WorkspaceMemberStatus.AllocatingSeat);
});
e2e(
'should accept invitation by link directly if status is pending on team workspace',
async t => {
const { owner, workspace } = await createTeamWorkspace(2);
const member = await app.create(Mockers.User);
e2e('should allocate seats', async t => {
const { owner, workspace } = await createTeamWorkspace();
await app.login(owner);
await app.login(owner);
// create a pending invitation
const invite = await app.gql({
query: inviteByEmailMutation,
variables: {
email: member.email,
workspaceId: workspace.id,
},
});
t.truthy(invite, 'failed to create invitation');
const u1 = await app.createUser();
await app.create(Mockers.WorkspaceUser, {
userId: u1.id,
workspaceId: workspace.id,
status: WorkspaceMemberStatus.AllocatingSeat,
source: 'Email',
});
const { createInviteLink } = await app.gql({
query: createInviteLinkMutation,
variables: {
workspaceId: workspace.id,
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
},
});
t.truthy(createInviteLink, 'failed to create invite link');
const link = createInviteLink.link;
const inviteLinkId = link.split('/').pop()!;
const u2 = await app.createUser();
await app.create(Mockers.WorkspaceUser, {
userId: u2.id,
workspaceId: workspace.id,
status: WorkspaceMemberStatus.AllocatingSeat,
source: 'Link',
});
// member accept invitation by link
await app.login(member);
await app.gql({
query: acceptInviteByInviteIdMutation,
variables: {
inviteId: inviteLinkId,
workspaceId: workspace.id,
},
});
await app.eventBus.emitAsync('workspace.members.allocateSeats', {
workspaceId: workspace.id,
quantity: 5,
});
const { getInviteInfo } = await app.gql({
query: getInviteInfoQuery,
variables: {
inviteId: invite.invite,
},
});
t.is(getInviteInfo.status, WorkspaceMemberStatus.Accepted);
}
);
const [members] = await app.models.workspaceUser.paginate(workspace.id, {
first: 10,
offset: 0,
});
t.is(
members.find(m => m.user.id === u1.id)?.status,
WorkspaceMemberStatus.Pending
);
t.is(
members.find(m => m.user.id === u2.id)?.status,
WorkspaceMemberStatus.Accepted
);
t.is(app.queue.count('notification.sendInvitation'), 1);
});
e2e('should set all rests to NeedMoreSeat', async t => {
const { owner, workspace } = await createTeamWorkspace();
await app.login(owner);
const u1 = await app.createUser();
await app.create(Mockers.WorkspaceUser, {
userId: u1.id,
workspaceId: workspace.id,
status: WorkspaceMemberStatus.AllocatingSeat,
source: 'Email',
});
const u2 = await app.createUser();
await app.create(Mockers.WorkspaceUser, {
userId: u2.id,
workspaceId: workspace.id,
status: WorkspaceMemberStatus.AllocatingSeat,
source: 'Email',
});
const u3 = await app.createUser();
await app.create(Mockers.WorkspaceUser, {
userId: u3.id,
workspaceId: workspace.id,
status: WorkspaceMemberStatus.AllocatingSeat,
source: 'Link',
});
await app.eventBus.emitAsync('workspace.members.allocateSeats', {
workspaceId: workspace.id,
quantity: 4,
});
const [members] = await app.models.workspaceUser.paginate(workspace.id, {
first: 10,
offset: 0,
});
t.is(
members.find(m => m.user.id === u2.id)?.status,
WorkspaceMemberStatus.NeedMoreSeat
);
t.is(
members.find(m => m.user.id === u3.id)?.status,
WorkspaceMemberStatus.NeedMoreSeat
);
});