mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-22 20:41:53 +03:00
328 lines
9.0 KiB
TypeScript
328 lines
9.0 KiB
TypeScript
/// <reference types="../src/global.d.ts" />
|
|
|
|
import { INestApplication } from '@nestjs/common';
|
|
import { WorkspaceMemberStatus } from '@prisma/client';
|
|
import type { TestFn } from 'ava';
|
|
import ava from 'ava';
|
|
|
|
import { AppModule } from '../src/app.module';
|
|
import { AuthService } from '../src/core/auth';
|
|
import { DocContentService } from '../src/core/doc-renderer';
|
|
import { Permission, PermissionService } from '../src/core/permission';
|
|
import {
|
|
QuotaManagementService,
|
|
QuotaService,
|
|
QuotaType,
|
|
} from '../src/core/quota';
|
|
import {
|
|
acceptInviteById,
|
|
createInviteLink,
|
|
createTestingApp,
|
|
createWorkspace,
|
|
getInviteInfo,
|
|
grantMember,
|
|
inviteUser,
|
|
inviteUsers,
|
|
leaveWorkspace,
|
|
PermissionEnum,
|
|
signUp,
|
|
sleep,
|
|
UserAuthedType,
|
|
} from './utils';
|
|
|
|
const test = ava as TestFn<{
|
|
app: INestApplication;
|
|
auth: AuthService;
|
|
quota: QuotaService;
|
|
quotaManager: QuotaManagementService;
|
|
permissions: PermissionService;
|
|
}>;
|
|
|
|
test.beforeEach(async t => {
|
|
const { app } = await createTestingApp({
|
|
imports: [AppModule],
|
|
tapModule: module => {
|
|
module.overrideProvider(DocContentService).useValue({
|
|
getWorkspaceContent() {
|
|
return {
|
|
name: 'test',
|
|
avatarKey: null,
|
|
};
|
|
},
|
|
});
|
|
},
|
|
});
|
|
|
|
const quota = app.get(QuotaService);
|
|
const quotaManager = app.get(QuotaManagementService);
|
|
const permissions = app.get(PermissionService);
|
|
const auth = app.get(AuthService);
|
|
|
|
t.context.app = app;
|
|
t.context.quota = quota;
|
|
t.context.quotaManager = quotaManager;
|
|
t.context.permissions = permissions;
|
|
t.context.auth = auth;
|
|
});
|
|
|
|
test.afterEach.always(async t => {
|
|
await t.context.app.close();
|
|
});
|
|
|
|
const init = async (app: INestApplication, memberLimit = 10) => {
|
|
const owner = await signUp(app, 'test', 'test@affine.pro', '123456');
|
|
const ws = await createWorkspace(app, owner.token.token);
|
|
|
|
const quota = app.get(QuotaManagementService);
|
|
await quota.addTeamWorkspace(ws.id, 'test');
|
|
await quota.updateWorkspaceConfig(ws.id, QuotaType.TeamPlanV1, {
|
|
memberLimit,
|
|
});
|
|
|
|
const invite = async (
|
|
email: string,
|
|
permission: PermissionEnum = 'Write'
|
|
) => {
|
|
const member = await signUp(app, email.split('@')[0], email, '123456');
|
|
const inviteId = await inviteUser(
|
|
app,
|
|
owner.token.token,
|
|
ws.id,
|
|
member.email,
|
|
permission
|
|
);
|
|
await acceptInviteById(app, ws.id, inviteId);
|
|
return member;
|
|
};
|
|
|
|
const inviteBatch = async (emails: string[]) => {
|
|
const members = [];
|
|
for (const email of emails) {
|
|
const member = await signUp(app, email.split('@')[0], email, '123456');
|
|
members.push(member);
|
|
}
|
|
const invites = await inviteUsers(app, owner.token.token, ws.id, emails);
|
|
return [members, invites] as const;
|
|
};
|
|
|
|
const getCreateInviteLinkFetcher = async () => {
|
|
const { link } = await createInviteLink(
|
|
app,
|
|
owner.token.token,
|
|
ws.id,
|
|
'OneDay'
|
|
);
|
|
const inviteId = link.split('/').pop()!;
|
|
return [
|
|
inviteId,
|
|
async (email: string): Promise<UserAuthedType> => {
|
|
const member = await signUp(app, email.split('@')[0], email, '123456');
|
|
await acceptInviteById(app, ws.id, inviteId, false, member.token.token);
|
|
return member;
|
|
},
|
|
async (token: string) => {
|
|
await acceptInviteById(app, ws.id, inviteId, false, token);
|
|
},
|
|
] as const;
|
|
};
|
|
|
|
const admin = await invite('admin@affine.pro', 'Admin');
|
|
const write = await invite('member1@affine.pro');
|
|
const read = await invite('member2@affine.pro', 'Read');
|
|
|
|
return {
|
|
invite,
|
|
inviteBatch,
|
|
createInviteLink: getCreateInviteLinkFetcher,
|
|
owner,
|
|
ws,
|
|
admin,
|
|
write,
|
|
read,
|
|
};
|
|
};
|
|
|
|
test('should be able to check seat limit', async t => {
|
|
const { app, permissions, quotaManager } = t.context;
|
|
const { invite, inviteBatch, ws } = await init(app, 4);
|
|
|
|
{
|
|
// invite
|
|
await t.throwsAsync(
|
|
invite('member3@affine.pro', 'Read'),
|
|
{ message: 'You have exceeded your workspace member quota.' },
|
|
'should throw error if exceed member limit'
|
|
);
|
|
await quotaManager.updateWorkspaceConfig(ws.id, QuotaType.TeamPlanV1, {
|
|
memberLimit: 5,
|
|
});
|
|
await t.notThrowsAsync(
|
|
invite('member4@affine.pro', 'Read'),
|
|
'should not throw error if not exceed member limit'
|
|
);
|
|
}
|
|
|
|
{
|
|
const members1 = inviteBatch(['member5@affine.pro']);
|
|
// invite batch
|
|
await t.notThrowsAsync(
|
|
members1,
|
|
'should not throw error in batch invite event reach limit'
|
|
);
|
|
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(
|
|
ws.id,
|
|
(await members1)[0][0].id
|
|
),
|
|
WorkspaceMemberStatus.NeedMoreSeat,
|
|
'should be able to check member status'
|
|
);
|
|
|
|
// refresh seat, fifo
|
|
sleep(1000);
|
|
const [[members2]] = await inviteBatch(['member6@affine.pro']);
|
|
await permissions.refreshSeatStatus(ws.id, 6);
|
|
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(
|
|
ws.id,
|
|
(await members1)[0][0].id
|
|
),
|
|
WorkspaceMemberStatus.Pending,
|
|
'should become accepted after refresh'
|
|
);
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(ws.id, members2.id),
|
|
WorkspaceMemberStatus.NeedMoreSeat,
|
|
'should not change status'
|
|
);
|
|
}
|
|
});
|
|
|
|
test('should be able to grant team member permission', async t => {
|
|
const { app, permissions } = t.context;
|
|
const { owner, ws, admin, write, read } = await init(app);
|
|
|
|
await t.throwsAsync(
|
|
grantMember(app, read.token.token, ws.id, write.id, 'Write'),
|
|
{ instanceOf: Error },
|
|
'should throw error if not owner'
|
|
);
|
|
await t.throwsAsync(
|
|
grantMember(app, write.token.token, ws.id, read.id, 'Write'),
|
|
{ instanceOf: Error },
|
|
'should throw error if not owner'
|
|
);
|
|
await t.throwsAsync(
|
|
grantMember(app, admin.token.token, ws.id, read.id, 'Write'),
|
|
{ instanceOf: Error },
|
|
'should throw error if not owner'
|
|
);
|
|
|
|
{
|
|
// owner should be able to grant permission
|
|
t.true(
|
|
await permissions.tryCheckWorkspaceIs(ws.id, read.id, Permission.Read),
|
|
'should be able to check permission'
|
|
);
|
|
t.truthy(
|
|
await grantMember(app, owner.token.token, ws.id, read.id, 'Admin'),
|
|
'should be able to grant permission'
|
|
);
|
|
t.true(
|
|
await permissions.tryCheckWorkspaceIs(ws.id, read.id, Permission.Admin),
|
|
'should be able to check permission'
|
|
);
|
|
}
|
|
});
|
|
|
|
test('should be able to leave workspace', async t => {
|
|
const { app } = t.context;
|
|
const { owner, ws, admin, write, read } = await init(app);
|
|
|
|
t.false(
|
|
await leaveWorkspace(app, owner.token.token, ws.id),
|
|
'owner should not be able to leave workspace'
|
|
);
|
|
t.true(
|
|
await leaveWorkspace(app, admin.token.token, ws.id),
|
|
'admin should be able to leave workspace'
|
|
);
|
|
t.true(
|
|
await leaveWorkspace(app, write.token.token, ws.id),
|
|
'write should be able to leave workspace'
|
|
);
|
|
t.true(
|
|
await leaveWorkspace(app, read.token.token, ws.id),
|
|
'read should be able to leave workspace'
|
|
);
|
|
});
|
|
|
|
test('should be able to invite by link', async t => {
|
|
const { app, permissions, quotaManager } = t.context;
|
|
const { createInviteLink, owner, ws } = await init(app, 4);
|
|
const [inviteId, invite, acceptInvite] = await createInviteLink();
|
|
|
|
{
|
|
// check invite link
|
|
const info = await getInviteInfo(app, owner.token.token, inviteId);
|
|
t.is(info.workspace.id, ws.id, 'should be able to get invite info');
|
|
}
|
|
|
|
{
|
|
// invite link
|
|
const members: UserAuthedType[] = [];
|
|
await t.notThrowsAsync(async () => {
|
|
members.push(await invite('member3@affine.pro'));
|
|
members.push(await invite('member4@affine.pro'));
|
|
}, 'should not throw error even exceed member limit');
|
|
const [m3, m4] = members;
|
|
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(ws.id, m3.id),
|
|
WorkspaceMemberStatus.NeedMoreSeatAndReview,
|
|
'should not change status'
|
|
);
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(ws.id, m4.id),
|
|
WorkspaceMemberStatus.NeedMoreSeatAndReview,
|
|
'should not change status'
|
|
);
|
|
|
|
await quotaManager.updateWorkspaceConfig(ws.id, QuotaType.TeamPlanV1, {
|
|
memberLimit: 5,
|
|
});
|
|
await permissions.refreshSeatStatus(ws.id, 5);
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(ws.id, m3.id),
|
|
WorkspaceMemberStatus.UnderReview,
|
|
'should not change status'
|
|
);
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(ws.id, m4.id),
|
|
WorkspaceMemberStatus.NeedMoreSeatAndReview,
|
|
'should not change status'
|
|
);
|
|
|
|
await quotaManager.updateWorkspaceConfig(ws.id, QuotaType.TeamPlanV1, {
|
|
memberLimit: 6,
|
|
});
|
|
await permissions.refreshSeatStatus(ws.id, 6);
|
|
t.is(
|
|
await permissions.getWorkspaceMemberStatus(ws.id, m4.id),
|
|
WorkspaceMemberStatus.UnderReview,
|
|
'should not change status'
|
|
);
|
|
|
|
{
|
|
const message = `You have already joined in Space ${ws.id}.`;
|
|
await t.throwsAsync(
|
|
acceptInvite(owner.token.token),
|
|
{ message },
|
|
'should throw error if member already in workspace'
|
|
);
|
|
}
|
|
}
|
|
});
|