feat(server): doc level permission (#9760)

close CLOUD-89 CLOUD-90 CLOUD-91 CLOUD-92
This commit is contained in:
Brooooooklyn
2025-02-05 07:06:57 +00:00
parent 64de83b13d
commit abeff8bb1a
36 changed files with 2257 additions and 324 deletions

View File

@@ -3,7 +3,8 @@ import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { Config } from '../../base/config';
import { Permission, PublicPageMode } from '../../models/common';
import { WorkspaceRole } from '../../core/permission';
import { PublicPageMode } from '../../models/common';
import { PageModel } from '../../models/page';
import { type User, UserModel } from '../../models/user';
import { type Workspace, WorkspaceModel } from '../../models/workspace';
@@ -131,7 +132,7 @@ test('should grant a member to access a page', async t => {
workspace.id,
'page1',
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.false(hasAccess);
// grant write permission
@@ -139,20 +140,20 @@ test('should grant a member to access a page', async t => {
workspace.id,
'page1',
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.true(hasAccess);
hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
user.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.true(hasAccess);
// delete member
@@ -174,14 +175,14 @@ test('should change the page owner', async t => {
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
);
t.true(
await t.context.page.isMember(
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
)
);
@@ -193,14 +194,14 @@ test('should change the page owner', async t => {
workspace.id,
'page1',
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.true(
await t.context.page.isMember(
workspace.id,
'page1',
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
)
);
t.false(
@@ -208,7 +209,7 @@ test('should change the page owner', async t => {
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
)
);
});
@@ -221,7 +222,7 @@ test('should not delete owner from page', async t => {
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
);
const count = await t.context.page.deleteMember(
workspace.id,

View File

@@ -4,7 +4,7 @@ import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
import { EmailAlreadyUsed, EventBus } from '../../base';
import { Permission } from '../../models/common';
import { WorkspaceRole } from '../../core/permission';
import { UserModel } from '../../models/user';
import { WorkspaceMemberStatus } from '../../models/workspace';
import { createTestingModule, initTestingDB } from '../utils';
@@ -263,7 +263,7 @@ test('should trigger user.deleted event', async t => {
public: false,
},
},
type: Permission.Owner,
type: WorkspaceRole.Owner,
status: WorkspaceMemberStatus.Accepted,
},
},

View File

@@ -4,7 +4,7 @@ import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
import { Config, EventBus } from '../../base';
import { Permission } from '../../models/common';
import { WorkspaceRole } from '../../core/permission';
import { UserModel } from '../../models/user';
import { WorkspaceModel } from '../../models/workspace';
import { createTestingModule, initTestingDB } from '../utils';
@@ -92,25 +92,25 @@ test('should workspace owner has all permissions', async t => {
let allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -127,32 +127,32 @@ test('should workspace admin has all permissions except owner', async t => {
data: {
workspaceId: workspace.id,
userId: otherUser.id,
type: Permission.Admin,
type: WorkspaceRole.Admin,
status: WorkspaceMemberStatus.Accepted,
},
});
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -169,32 +169,32 @@ test('should workspace write has write and read permissions', async t => {
data: {
workspaceId: workspace.id,
userId: otherUser.id,
type: Permission.Write,
type: WorkspaceRole.Collaborator,
status: WorkspaceMemberStatus.Accepted,
},
});
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -211,32 +211,26 @@ test('should workspace read has read permission only', async t => {
data: {
workspaceId: workspace.id,
userId: otherUser.id,
type: Permission.Read,
type: WorkspaceRole.Collaborator,
status: WorkspaceMemberStatus.Accepted,
},
});
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -252,25 +246,25 @@ test('should user not in workspace has no permissions', async t => {
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, false);
});
@@ -313,7 +307,7 @@ test('should grant member with read permission and Pending status by default', a
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Pending);
// grant again should do nothing
@@ -344,18 +338,18 @@ test('should grant Pending status member to Accepted status', async t => {
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Pending);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -370,27 +364,27 @@ test('should grant new owner and change exists owner to admin', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Accepted);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Owner,
WorkspaceRole.Owner,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Owner);
t.is(member2.type, WorkspaceRole.Owner);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
// check old owner
const owner = await t.context.workspace.getMember(workspace.id, user.id);
t.is(owner!.type, Permission.Admin);
t.is(owner!.type, WorkspaceRole.Admin);
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
});
@@ -405,23 +399,23 @@ test('should grant write permission on exists member', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Accepted);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Write,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Write);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -436,23 +430,23 @@ test('should grant UnderReview status member to Accepted status', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.UnderReview
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.UnderReview);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -467,23 +461,23 @@ test('should grant NeedMoreSeat status member to Pending status', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeat
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Pending);
});
@@ -498,23 +492,23 @@ test('should grant NeedMoreSeatAndReview status member to UnderReview status', a
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeatAndReview
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.UnderReview
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.UnderReview);
});
@@ -532,19 +526,19 @@ test('should grant Pending status member to write permission and Accepted status
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Pending);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Write,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
// TODO(fengmk2): fix this
// t.is(member2.type, Permission.Write);
// t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -559,23 +553,23 @@ test('should grant no thing on invalid status', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeat
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.NeedMoreSeat);
});
@@ -590,7 +584,7 @@ test('should get the accepted status workspace member', async t => {
await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
const member = await t.context.workspace.getMember(
@@ -599,7 +593,7 @@ test('should get the accepted status workspace member', async t => {
);
t.is(member!.workspaceId, workspace.id);
t.is(member!.userId, otherUser.id);
t.is(member!.type, Permission.Read);
t.is(member!.type, WorkspaceRole.Collaborator);
t.is(member!.status, WorkspaceMemberStatus.Accepted);
});
@@ -614,7 +608,7 @@ test('should get any status workspace member, including pending and accepted', a
await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
const member = await t.context.workspace.getMemberInAnyStatus(
@@ -623,7 +617,7 @@ test('should get any status workspace member, including pending and accepted', a
);
t.is(member!.workspaceId, workspace.id);
t.is(member!.userId, otherUser.id);
t.is(member!.type, Permission.Read);
t.is(member!.type, WorkspaceRole.Collaborator);
t.is(member!.status, WorkspaceMemberStatus.Pending);
});
@@ -635,7 +629,7 @@ test('should get workspace owner by workspace id', async t => {
const owner = await t.context.workspace.getOwner(workspace.id);
t.is(owner!.workspaceId, workspace.id);
t.is(owner!.userId, user.id);
t.is(owner!.type, Permission.Owner);
t.is(owner!.type, WorkspaceRole.Owner);
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
t.truthy(owner!.user);
t.deepEqual(owner!.user, user);
@@ -658,27 +652,27 @@ test('should find workspace admin by workspace id', async t => {
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Admin,
WorkspaceRole.Admin,
WorkspaceMemberStatus.Accepted
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
// pending member should not be admin
await t.context.workspace.grantMember(
workspace.id,
otherUser3.id,
Permission.Admin,
WorkspaceRole.Admin,
WorkspaceMemberStatus.Pending
);
const members = await t.context.workspace.findAdmins(workspace.id);
t.is(members.length, 1);
t.is(members[0].workspaceId, workspace.id);
t.is(members[0].userId, otherUser1.id);
t.is(members[0].type, Permission.Admin);
t.is(members[0].type, WorkspaceRole.Admin);
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
});
@@ -710,13 +704,13 @@ test('should the workspace member total count, including pending and accepted',
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
const count = await t.context.workspace.getMemberTotalCount(workspace.id);
@@ -737,13 +731,13 @@ test('should the workspace member used count, only count the accepted member', a
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
const count = await t.context.workspace.getMemberUsedCount(workspace.id);
@@ -855,7 +849,7 @@ test('should delete workspace member in Pending, Accepted status', async t => {
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
@@ -874,7 +868,7 @@ test('should trigger workspace.members.requestDeclined event when delete workspa
const member = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.UnderReview
);
t.is(member.status, WorkspaceMemberStatus.UnderReview);
@@ -919,7 +913,7 @@ test('should trigger workspace.members.requestDeclined event when delete workspa
const member = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeatAndReview
);
t.is(member.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
@@ -970,19 +964,19 @@ test('should refresh member seat status', async t => {
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeatAndReview
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
await t.context.workspace.grantMember(
workspace.id,
otherUser3.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeat
);
let count = await t.context.db.workspaceUserPermission.count({
@@ -1043,30 +1037,30 @@ test('should find the workspace members order by type:desc and createdAt:asc', a
await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
}
let members = await t.context.workspace.findMembers(workspace.id);
t.is(members.length, 8);
t.is(members[0].type, Permission.Owner);
t.is(members[0].type, WorkspaceRole.Owner);
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
for (let i = 1; i < 8; i++) {
t.is(members[i].type, Permission.Read);
t.is(members[i].type, WorkspaceRole.Collaborator);
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
}
members = await t.context.workspace.findMembers(workspace.id, { take: 100 });
t.is(members.length, 11);
t.is(members[0].type, Permission.Owner);
t.is(members[0].type, WorkspaceRole.Owner);
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
for (let i = 1; i < 11; i++) {
t.is(members[i].type, Permission.Read);
t.is(members[i].type, WorkspaceRole.Collaborator);
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
}
// skip should work
members = await t.context.workspace.findMembers(workspace.id, { skip: 5 });
t.is(members.length, 6);
t.is(members[0].type, Permission.Read);
t.is(members[0].type, WorkspaceRole.Collaborator);
});
test('should get the workspace member invitation', async t => {

View File

@@ -13,7 +13,7 @@ import { AppModule } from '../app.module';
import { EventBus } from '../base';
import { AuthService } from '../core/auth';
import { DocContentService } from '../core/doc-renderer';
import { Permission, PermissionService } from '../core/permission';
import { PermissionService, WorkspaceRole } from '../core/permission';
import { QuotaManagementService, QuotaService, QuotaType } from '../core/quota';
import { WorkspaceType } from '../core/workspaces';
import {
@@ -29,7 +29,6 @@ import {
inviteUser,
inviteUsers,
leaveWorkspace,
PermissionEnum,
revokeInviteLink,
revokeMember,
revokeUser,
@@ -105,7 +104,7 @@ const init = async (
const invite = async (
email: string,
permission: PermissionEnum = 'Write',
permission: WorkspaceRole = WorkspaceRole.Collaborator,
shouldSendEmail: boolean = false
) => {
const member = await signUp(app, email.split('@')[0], email, '123456');
@@ -193,9 +192,12 @@ const init = async (
] as const;
};
const admin = await invite(`${prefix}admin@affine.pro`, 'Admin');
const admin = await invite(`${prefix}admin@affine.pro`, WorkspaceRole.Admin);
const write = await invite(`${prefix}write@affine.pro`);
const read = await invite(`${prefix}read@affine.pro`, 'Read');
const read = await invite(
`${prefix}read@affine.pro`,
WorkspaceRole.Collaborator
);
return {
invite,
@@ -268,7 +270,7 @@ test('should be able to check seat limit', async t => {
{
// invite
await t.throwsAsync(
invite('member3@affine.pro', 'Read'),
invite('member3@affine.pro', WorkspaceRole.Collaborator),
{ message: 'You have exceeded your workspace member quota.' },
'should throw error if exceed member limit'
);
@@ -276,7 +278,7 @@ test('should be able to check seat limit', async t => {
memberLimit: 5,
});
await t.notThrowsAsync(
invite('member4@affine.pro', 'Read'),
invite('member4@affine.pro', WorkspaceRole.Collaborator),
'should not throw error if not exceed member limit'
);
}
@@ -324,17 +326,35 @@ test('should be able to grant team member permission', async t => {
const { owner, teamWorkspace: ws, admin, write, read } = await init(app);
await t.throwsAsync(
grantMember(app, read.token.token, ws.id, write.id, 'Write'),
grantMember(
app,
read.token.token,
ws.id,
write.id,
WorkspaceRole.Collaborator
),
{ instanceOf: Error },
'should throw error if not owner'
);
await t.throwsAsync(
grantMember(app, write.token.token, ws.id, read.id, 'Write'),
grantMember(
app,
write.token.token,
ws.id,
read.id,
WorkspaceRole.Collaborator
),
{ instanceOf: Error },
'should throw error if not owner'
);
await t.throwsAsync(
grantMember(app, admin.token.token, ws.id, read.id, 'Write'),
grantMember(
app,
admin.token.token,
ws.id,
read.id,
WorkspaceRole.Collaborator
),
{ instanceOf: Error },
'should throw error if not owner'
);
@@ -342,15 +362,29 @@ test('should be able to grant team member permission', async t => {
{
// owner should be able to grant permission
t.true(
await permissions.tryCheckWorkspaceIs(ws.id, read.id, Permission.Read),
await permissions.tryCheckWorkspaceIs(
ws.id,
read.id,
WorkspaceRole.Collaborator
),
'should be able to check permission'
);
t.truthy(
await grantMember(app, owner.token.token, ws.id, read.id, 'Admin'),
await grantMember(
app,
owner.token.token,
ws.id,
read.id,
WorkspaceRole.Admin
),
'should be able to grant permission'
);
t.true(
await permissions.tryCheckWorkspaceIs(ws.id, read.id, Permission.Admin),
await permissions.tryCheckWorkspaceIs(
ws.id,
read.id,
WorkspaceRole.Admin
),
'should be able to check permission'
);
}
@@ -692,17 +726,33 @@ test('should be able to emit events', async t => {
{
const { teamWorkspace: tws, owner, read } = await init(app);
await grantMember(app, owner.token.token, tws.id, read.id, 'Admin');
await grantMember(
app,
owner.token.token,
tws.id,
read.id,
WorkspaceRole.Admin
);
t.deepEqual(
event.emit.lastCall.args,
[
'workspace.members.roleChanged',
{ userId: read.id, workspaceId: tws.id, permission: Permission.Admin },
{
userId: read.id,
workspaceId: tws.id,
permission: WorkspaceRole.Admin,
},
],
'should emit role changed event'
);
await grantMember(app, owner.token.token, tws.id, read.id, 'Owner');
await grantMember(
app,
owner.token.token,
tws.id,
read.id,
WorkspaceRole.Owner
);
const [ownershipTransferred] = event.emit
.getCalls()
.map(call => call.args)

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
import { Permission } from '../../core/permission';
import { WorkspaceRole } from '../../core/permission';
import { UserType } from '../../core/user/types';
@Injectable()
@@ -14,7 +14,7 @@ export class WorkspaceResolverMock {
public: false,
permissions: {
create: {
type: Permission.Owner,
type: WorkspaceRole.Owner,
userId: user.id,
accepted: true,
status: WorkspaceMemberStatus.Accepted,

View File

@@ -1,4 +1,8 @@
import { INestApplication, ModuleMetadata } from '@nestjs/common';
import {
ConsoleLogger,
INestApplication,
ModuleMetadata,
} from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { Query, Resolver } from '@nestjs/graphql';
import { Test, TestingModuleBuilder } from '@nestjs/testing';
@@ -15,8 +19,6 @@ import { AuthGuard, AuthModule } from '../../core/auth';
import { UserFeaturesInit1698652531198 } from '../../data/migrations/1698652531198-user-features-init';
import { ModelsModule } from '../../models';
export type PermissionEnum = 'Owner' | 'Admin' | 'Write' | 'Read';
async function flushDB(client: PrismaClient) {
const result: { tablename: string }[] =
await client.$queryRaw`SELECT tablename
@@ -133,8 +135,11 @@ export async function createTestingApp(moduleDef: TestingModuleMeatdata = {}) {
cors: true,
bodyParser: true,
rawBody: true,
logger: ['fatal'],
});
const logger = new ConsoleLogger();
logger.setLogLevels(['fatal']);
app.useLogger(logger);
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
app.use(

View File

@@ -1,9 +1,9 @@
import type { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { WorkspaceRole } from '../../core/permission/types';
import type { WorkspaceType } from '../../core/workspaces';
import { gql } from './common';
import { PermissionEnum } from './utils';
export async function createWorkspace(
app: INestApplication,
@@ -157,7 +157,7 @@ export async function grantMember(
token: string,
workspaceId: string,
userId: string,
permission: PermissionEnum
permission: WorkspaceRole
) {
const res = await request(app.getHttpServer())
.post(gql)
@@ -169,7 +169,7 @@ export async function grantMember(
grantMember(
workspaceId: "${workspaceId}"
userId: "${userId}"
permission: ${permission}
permission: ${WorkspaceRole[permission]}
)
}
`,

View File

@@ -5,11 +5,13 @@ import ava from 'ava';
import request from 'supertest';
import { AppModule } from '../app.module';
import { WorkspaceRole } from '../core/permission/types';
import {
acceptInviteById,
createTestingApp,
createWorkspace,
getWorkspacePublicPages,
grantMember,
inviteUser,
publishPage,
revokePublicPage,
@@ -116,7 +118,7 @@ test('should share a page', async t => {
const msg1 = await publishPage(app, u2.token.token, 'not_exists_ws', 'page2');
t.is(
msg1,
'You do not have permission to access Space not_exists_ws.',
'You do not have permission to access doc page2 under Space not_exists_ws.',
'unauthorized user can share page'
);
const msg2 = await revokePublicPage(
@@ -127,7 +129,7 @@ test('should share a page', async t => {
);
t.is(
msg2,
'You do not have permission to access Space not_exists_ws.',
'You do not have permission to access doc page2 under Space not_exists_ws.',
'unauthorized user can share page'
);
@@ -136,6 +138,21 @@ test('should share a page', async t => {
workspace.id,
await inviteUser(app, u1.token.token, workspace.id, u2.email)
);
const msg3 = await publishPage(app, u2.token.token, workspace.id, 'page2');
t.is(
msg3,
`You do not have permission to access doc page2 under Space ${workspace.id}.`,
'WorkspaceRole and PageRole is lower than required'
);
await grantMember(
app,
u1.token.token,
workspace.id,
u2.id,
WorkspaceRole.Admin
);
const invited = await publishPage(app, u2.token.token, workspace.id, 'page2');
t.is(invited.id, 'page2', 'failed to share page');
@@ -154,21 +171,21 @@ test('should share a page', async t => {
t.is(pages2.length, 1, 'failed to get shared pages');
t.is(pages2[0].id, 'page2', 'failed to get shared page: page2');
const msg3 = await revokePublicPage(
const msg4 = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page3'
);
t.is(msg3, 'Page is not public');
t.is(msg4, 'Page is not public');
const msg4 = await revokePublicPage(
const revoked = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page2'
);
t.false(msg4.public, 'failed to revoke page');
t.false(revoked.public, 'failed to revoke page');
const page3 = await getWorkspacePublicPages(
app,
u1.token.token,
@@ -177,7 +194,7 @@ test('should share a page', async t => {
t.is(page3.length, 0, 'failed to get shared pages');
});
test('should can get workspace doc', async t => {
test('should be able to get workspace doc', async t => {
const { app } = t.context;
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '2');