refactor(server): permission (#10449)

This commit is contained in:
liuyi
2025-03-05 15:57:00 +08:00
committed by GitHub
parent bf7b1646b3
commit 61162c59fc
61 changed files with 2680 additions and 3562 deletions

View File

@@ -0,0 +1,160 @@
import { PrismaClient } from '@prisma/client';
import test from 'ava';
import Sinon from 'sinon';
import { EventBus } from '../../base';
import { DocRole, Models } from '../../models';
import { createTestingModule, TestingModule } from '../utils';
let db: PrismaClient;
let models: Models;
let module: TestingModule;
test.before(async () => {
module = await createTestingModule({
tapModule: m => {
m.overrideProvider(EventBus).useValue(Sinon.createStubInstance(EventBus));
},
});
models = module.get(Models);
db = module.get(PrismaClient);
});
test.beforeEach(async () => {
await module.initTestingDB();
Sinon.reset();
});
test.after(async () => {
await module.close();
});
async function create() {
return db.workspace.create({
data: { public: false },
});
}
test('should set doc owner', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
const docId = 'fake-doc-id';
await models.docUser.setOwner(workspace.id, docId, user.id);
const role = await models.docUser.get(workspace.id, docId, user.id);
t.is(role?.type, DocRole.Owner);
});
test('should transfer doc owner', async t => {
const user = await models.user.create({ email: 'u1@affine.pro' });
const user2 = await models.user.create({ email: 'u2@affine.pro' });
const workspace = await create();
const docId = 'fake-doc-id';
await models.docUser.setOwner(workspace.id, docId, user.id);
await models.docUser.setOwner(workspace.id, docId, user2.id);
const oldOwnerRole = await models.docUser.get(workspace.id, docId, user.id);
const newOwnerRole = await models.docUser.get(workspace.id, docId, user2.id);
t.is(oldOwnerRole?.type, DocRole.Manager);
t.is(newOwnerRole?.type, DocRole.Owner);
});
test('should set doc user role', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
const docId = 'fake-doc-id';
await models.docUser.set(workspace.id, docId, user.id, DocRole.Manager);
const role = await models.docUser.get(workspace.id, docId, user.id);
t.is(role?.type, DocRole.Manager);
});
test('should not allow setting doc owner through setDocUserRole', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
const docId = 'fake-doc-id';
await t.throwsAsync(
models.docUser.set(workspace.id, docId, user.id, DocRole.Owner),
{ message: 'Cannot set Owner role of a doc to a user.' }
);
});
test('should delete doc user role', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
const docId = 'fake-doc-id';
await models.docUser.set(workspace.id, docId, user.id, DocRole.Manager);
await models.docUser.delete(workspace.id, docId, user.id);
const role = await models.docUser.get(workspace.id, docId, user.id);
t.is(role, null);
});
test('should paginate doc user roles', async t => {
const workspace = await create();
const docId = 'fake-doc-id';
await db.user.createMany({
data: Array.from({ length: 200 }, (_, i) => ({
id: String(i),
name: `u${i}`,
email: `${i}@affine.pro`,
})),
});
await db.workspaceDocUserRole.createMany({
data: Array.from({ length: 200 }, (_, i) => ({
workspaceId: workspace.id,
docId,
userId: String(i),
type: DocRole.Editor,
createdAt: new Date(Date.now() + i * 1000),
})),
});
const [roles, total] = await models.docUser.paginate(workspace.id, docId, {
first: 10,
offset: 0,
});
t.is(roles.length, 10);
t.is(total, 200);
const [roles2] = await models.docUser.paginate(workspace.id, docId, {
after: roles.at(-1)?.createdAt.toISOString(),
first: 50,
offset: 0,
});
t.is(roles2.length, 50);
t.not(roles2[0].type, DocRole.Owner);
t.deepEqual(
roles2.map(r => r.userId),
roles2
.toSorted((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
.map(r => r.userId)
);
});
test('should count doc user roles', async t => {
const workspace = await create();
const docId = 'fake-doc-id';
const users = await Promise.all([
models.user.create({ email: 'u1@affine.pro' }),
models.user.create({ email: 'u2@affine.pro' }),
]);
await Promise.all(
users.map(user =>
models.docUser.set(workspace.id, docId, user.id, DocRole.Manager)
)
);
const count = await models.docUser.count(workspace.id, docId);
t.is(count, 2);
});

View File

@@ -1,232 +0,0 @@
import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { Config } from '../../base/config';
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';
import { createTestingModule, type TestingModule } from '../utils';
interface Context {
config: Config;
module: TestingModule;
db: PrismaClient;
user: UserModel;
workspace: WorkspaceModel;
page: PageModel;
}
const test = ava as TestFn<Context>;
test.before(async t => {
const module = await createTestingModule();
t.context.user = module.get(UserModel);
t.context.workspace = module.get(WorkspaceModel);
t.context.page = module.get(PageModel);
t.context.db = module.get(PrismaClient);
t.context.config = module.get(Config);
t.context.module = module;
});
let user: User;
let workspace: Workspace;
test.beforeEach(async t => {
await t.context.module.initTestingDB();
user = await t.context.user.create({
email: 'test@affine.pro',
});
workspace = await t.context.workspace.create(user.id);
});
test.after(async t => {
await t.context.module.close();
});
test('should create page with default mode and public false', async t => {
const page = await t.context.page.upsert(workspace.id, 'page1');
t.is(page.workspaceId, workspace.id);
t.is(page.docId, 'page1');
t.is(page.mode, PublicPageMode.Page);
t.is(page.public, false);
});
test('should update page', async t => {
const page = await t.context.page.upsert(workspace.id, 'page1');
const data = {
mode: PublicPageMode.Edgeless,
public: true,
};
await t.context.page.upsert(workspace.id, 'page1', data);
const page1 = await t.context.page.get(workspace.id, 'page1');
t.deepEqual(page1, {
...page,
...data,
});
});
test('should get null when page not exists', async t => {
const page = await t.context.page.get(workspace.id, 'page1');
t.is(page, null);
});
test('should get page by id and public flag', async t => {
await t.context.page.upsert(workspace.id, 'page1');
await t.context.page.upsert(workspace.id, 'page2', {
public: true,
});
let page1 = await t.context.page.get(workspace.id, 'page1');
t.is(page1!.public, false);
page1 = await t.context.page.get(workspace.id, 'page1', true);
t.is(page1, null);
let page2 = await t.context.page.get(workspace.id, 'page2', true);
t.is(page2!.public, true);
page2 = await t.context.page.get(workspace.id, 'page2', false);
t.is(page2, null);
});
test('should get public page count', async t => {
await t.context.page.upsert(workspace.id, 'page1', {
public: true,
});
await t.context.page.upsert(workspace.id, 'page2', {
public: true,
});
await t.context.page.upsert(workspace.id, 'page3');
const count = await t.context.page.getPublicsCount(workspace.id);
t.is(count, 2);
});
test('should get public pages of a workspace', async t => {
await t.context.page.upsert(workspace.id, 'page1', {
public: true,
});
await t.context.page.upsert(workspace.id, 'page2', {
public: true,
});
await t.context.page.upsert(workspace.id, 'page3');
const pages = await t.context.page.findPublics(workspace.id);
t.is(pages.length, 2);
t.deepEqual(pages.map(p => p.docId).sort(), ['page1', 'page2']);
});
test('should grant a member to access a page', async t => {
await t.context.page.upsert(workspace.id, 'page1', {
public: true,
});
const member = await t.context.user.create({
email: 'test1@affine.pro',
});
await t.context.page.grantMember(workspace.id, 'page1', member.id);
let hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
member.id
);
t.true(hasAccess);
hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Collaborator
);
t.false(hasAccess);
// grant write permission
await t.context.page.grantMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Collaborator
);
hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Collaborator
);
t.true(hasAccess);
hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Collaborator
);
t.true(hasAccess);
// delete member
const count = await t.context.page.deleteMember(
workspace.id,
'page1',
user.id
);
t.is(count, 1);
hasAccess = await t.context.page.isMember(workspace.id, 'page1', user.id);
t.false(hasAccess);
});
test('should change the page owner', async t => {
await t.context.page.upsert(workspace.id, 'page1', {
public: true,
});
await t.context.page.grantMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Owner
);
t.true(
await t.context.page.isMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Owner
)
);
// change owner
const otherUser = await t.context.user.create({
email: 'test1@affine.pro',
});
await t.context.page.grantMember(
workspace.id,
'page1',
otherUser.id,
WorkspaceRole.Owner
);
t.true(
await t.context.page.isMember(
workspace.id,
'page1',
otherUser.id,
WorkspaceRole.Owner
)
);
t.false(
await t.context.page.isMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Owner
)
);
});
test('should not delete owner from page', async t => {
await t.context.page.upsert(workspace.id, 'page1', {
public: true,
});
await t.context.page.grantMember(
workspace.id,
'page1',
user.id,
WorkspaceRole.Owner
);
const count = await t.context.page.deleteMember(
workspace.id,
'page1',
user.id
);
t.is(count, 0);
});

View File

@@ -2,13 +2,13 @@ import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
import { EmailAlreadyUsed, EventBus } from '../../base';
import { WorkspaceRole } from '../../core/permission';
import { Models } from '../../models';
import { UserModel } from '../../models/user';
import { WorkspaceMemberStatus } from '../../models/workspace';
import { createTestingModule, sleep, type TestingModule } from '../utils';
interface Context {
module: TestingModule;
models: Models;
user: UserModel;
}
@@ -18,6 +18,7 @@ test.before(async t => {
const module = await createTestingModule({});
t.context.user = module.get(UserModel);
t.context.models = module.get(Models);
t.context.module = module;
});
@@ -253,24 +254,13 @@ test('should trigger user.deleted event', async t => {
const user = await t.context.user.create({
email: 'test@affine.pro',
workspacePermissions: {
create: {
workspace: {
create: {
id: 'test-workspace',
public: false,
},
},
type: WorkspaceRole.Owner,
status: WorkspaceMemberStatus.Accepted,
},
},
});
const workspace = await t.context.models.workspace.create(user.id);
await t.context.user.delete(user.id);
t.true(
spy.calledOnceWithExactly({ ...user, ownedWorkspaces: ['test-workspace'] })
spy.calledOnceWithExactly({ ...user, ownedWorkspaces: [workspace.id] })
);
// await for 'user.deleted' event to be emitted and executed
// avoid race condition cause database dead lock

View File

@@ -0,0 +1,255 @@
import { PrismaClient } from '@prisma/client';
import test from 'ava';
import Sinon from 'sinon';
import { EventBus } from '../../base';
import { Models, WorkspaceMemberStatus, WorkspaceRole } from '../../models';
import { createTestingModule, TestingModule } from '../utils';
let db: PrismaClient;
let models: Models;
let module: TestingModule;
let event: Sinon.SinonStubbedInstance<EventBus>;
test.before(async () => {
module = await createTestingModule({
tapModule: m => {
m.overrideProvider(EventBus).useValue(Sinon.createStubInstance(EventBus));
},
});
models = module.get(Models);
event = module.get(EventBus);
db = module.get(PrismaClient);
});
test.beforeEach(async () => {
await module.initTestingDB();
Sinon.reset();
});
test.after(async () => {
await module.close();
});
async function create() {
return db.workspace.create({
data: { public: false },
});
}
test('should set workspace owner', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
await models.workspaceUser.setOwner(workspace.id, user.id);
const owner = await models.workspaceUser.getOwner(workspace.id);
t.is(owner.id, user.id);
});
test('should transfer workespace owner', async t => {
const user = await models.user.create({ email: 'u1@affine.pro' });
const user2 = await models.user.create({ email: 'u2@affine.pro' });
const workspace = await models.workspace.create(user.id);
await models.workspaceUser.setOwner(workspace.id, user2.id);
t.true(
event.emit.lastCall.calledWith('workspace.owner.changed', {
workspaceId: workspace.id,
from: user.id,
to: user2.id,
})
);
const owner2 = await models.workspaceUser.getOwner(workspace.id);
t.is(owner2.id, user2.id);
});
test('should get user role', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
await models.workspaceUser.set(workspace.id, user.id, WorkspaceRole.Admin);
const role = await models.workspaceUser.get(workspace.id, user.id);
t.is(role!.type, WorkspaceRole.Admin);
});
test('should get active workspace role', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
await models.workspaceUser.set(
workspace.id,
user.id,
WorkspaceRole.Admin,
WorkspaceMemberStatus.Accepted
);
const role = await models.workspaceUser.getActive(workspace.id, user.id);
t.is(role!.type, WorkspaceRole.Admin);
});
test('should not get inactive workspace role', async t => {
const workspace = await create();
const u1 = await models.user.create({ email: 'u1@affine.pro' });
await models.workspaceUser.set(workspace.id, u1.id, WorkspaceRole.Admin);
let role = await models.workspaceUser.getActive(workspace.id, u1.id);
t.is(role, null);
await models.workspaceUser.setStatus(
workspace.id,
u1.id,
WorkspaceMemberStatus.UnderReview
);
role = await models.workspaceUser.getActive(workspace.id, u1.id);
t.is(role, null);
});
test('should update user role', async t => {
const workspace = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
await models.workspaceUser.set(
workspace.id,
user.id,
WorkspaceRole.Admin,
WorkspaceMemberStatus.Accepted
);
const role = await models.workspaceUser.get(workspace.id, user.id);
t.is(role!.type, WorkspaceRole.Admin);
await models.workspaceUser.set(
workspace.id,
user.id,
WorkspaceRole.Collaborator
);
const role2 = await models.workspaceUser.get(workspace.id, user.id);
t.is(role2!.type, WorkspaceRole.Collaborator);
t.deepEqual(event.emit.lastCall.args, [
'workspace.members.roleChanged',
{
userId: user.id,
workspaceId: workspace.id,
role: WorkspaceRole.Collaborator,
},
]);
});
test('should return workspace role if status is Accepted', async t => {
const workspace = await create();
const u1 = await models.user.create({ email: 'u1@affine.pro' });
await models.workspaceUser.set(workspace.id, u1.id, WorkspaceRole.Admin);
await models.workspaceUser.setStatus(
workspace.id,
u1.id,
WorkspaceMemberStatus.Accepted
);
const role = await models.workspaceUser.get(workspace.id, u1.id);
t.is(role!.type, WorkspaceRole.Admin);
});
test('should delete workspace user role', async t => {
const workspace = await create();
const u1 = await models.user.create({ email: 'u1@affine.pro' });
await models.workspaceUser.set(workspace.id, u1.id, WorkspaceRole.Admin);
await models.workspaceUser.setStatus(
workspace.id,
u1.id,
WorkspaceMemberStatus.Accepted
);
let role = await models.workspaceUser.get(workspace.id, u1.id);
t.is(role!.type, WorkspaceRole.Admin);
await models.workspaceUser.delete(workspace.id, u1.id);
role = await models.workspaceUser.get(workspace.id, u1.id);
t.is(role, null);
});
test('should get user workspace roles with filter', async t => {
const ws1 = await create();
const ws2 = await create();
const user = await models.user.create({ email: 'u1@affine.pro' });
await db.workspaceUserRole.createMany({
data: [
{
workspaceId: ws1.id,
userId: user.id,
type: WorkspaceRole.Admin,
status: WorkspaceMemberStatus.Accepted,
},
{
workspaceId: ws2.id,
userId: user.id,
type: WorkspaceRole.Collaborator,
status: WorkspaceMemberStatus.Accepted,
},
],
});
let roles = await models.workspaceUser.getUserActiveRoles(user.id, {
role: WorkspaceRole.Admin,
});
t.is(roles.length, 1);
t.is(roles[0].type, WorkspaceRole.Admin);
roles = await models.workspaceUser.getUserActiveRoles(user.id);
t.is(roles.length, 2);
});
test('should paginate workspace user roles', async t => {
const workspace = await create();
await db.user.createMany({
data: Array.from({ length: 200 }, (_, i) => ({
id: String(i),
name: `u${i}`,
email: `${i}@affine.pro`,
})),
});
await db.workspaceUserRole.createMany({
data: Array.from({ length: 200 }, (_, i) => ({
workspaceId: workspace.id,
userId: String(i),
type: WorkspaceRole.Collaborator,
status: Object.values(WorkspaceMemberStatus)[
Math.floor(Math.random() * Object.values(WorkspaceMemberStatus).length)
],
createdAt: new Date(Date.now() + i * 1000),
})),
});
const [roles, total] = await models.workspaceUser.paginate(workspace.id, {
first: 10,
offset: 0,
});
t.is(roles.length, 10);
t.is(total, 200);
const [roles2] = await models.workspaceUser.paginate(workspace.id, {
after: roles.at(-1)?.createdAt.toISOString(),
first: 50,
offset: 0,
});
t.is(roles2.length, 50);
t.deepEqual(
roles2.map(r => r.id),
roles2
.toSorted((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
.map(r => r.id)
);
});

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ import { AppModule } from '../app.module';
import { EventBus } from '../base';
import { AuthService } from '../core/auth';
import { DocReader } from '../core/doc';
import { DocRole, PermissionService, WorkspaceRole } from '../core/permission';
import { DocRole, WorkspaceRole } from '../core/permission';
import { WorkspaceType } from '../core/workspaces';
import { Models } from '../models';
import {
@@ -43,7 +43,6 @@ const test = ava as TestFn<{
auth: AuthService;
event: Sinon.SinonStubbedInstance<EventBus>;
models: Models;
permissions: PermissionService;
}>;
test.before(async t => {
@@ -68,7 +67,6 @@ test.before(async t => {
t.context.auth = app.get(AuthService);
t.context.event = app.get(EventBus);
t.context.models = app.get(Models);
t.context.permissions = app.get(PermissionService);
});
test.beforeEach(async t => {
@@ -256,7 +254,7 @@ test('should be able to invite multiple users', async t => {
});
test('should be able to check seat limit', async t => {
const { app, permissions, models } = t.context;
const { app, models } = t.context;
const { invite, inviteBatch, teamWorkspace: ws } = await init(app, 5);
{
@@ -284,10 +282,8 @@ test('should be able to check seat limit', async t => {
);
t.is(
await permissions.getWorkspaceMemberStatus(
ws.id,
(await members1)[0][0].id
),
(await models.workspaceUser.get(ws.id, (await members1)[0][0].id))
?.status,
WorkspaceMemberStatus.NeedMoreSeat,
'should be able to check member status'
);
@@ -295,18 +291,16 @@ test('should be able to check seat limit', async t => {
// refresh seat, fifo
await sleep(1000);
const [[members2]] = await inviteBatch(['member6@affine.pro']);
await permissions.refreshSeatStatus(ws.id, 7);
await models.workspaceUser.refresh(ws.id, 7);
t.is(
await permissions.getWorkspaceMemberStatus(
ws.id,
(await members1)[0][0].id
),
(await models.workspaceUser.get(ws.id, (await members1)[0][0].id))
?.status,
WorkspaceMemberStatus.Pending,
'should become accepted after refresh'
);
t.is(
await permissions.getWorkspaceMemberStatus(ws.id, members2.id),
(await models.workspaceUser.get(ws.id, members2.id))?.status,
WorkspaceMemberStatus.NeedMoreSeat,
'should not change status'
);
@@ -314,7 +308,7 @@ test('should be able to check seat limit', async t => {
});
test('should be able to grant team member permission', async t => {
const { app, permissions } = t.context;
const { app, models } = t.context;
const { owner, teamWorkspace: ws, write, read } = await init(app);
app.switchUser(read);
@@ -335,11 +329,8 @@ test('should be able to grant team member permission', async t => {
// owner should be able to grant permission
app.switchUser(owner);
t.true(
await permissions.tryCheckWorkspaceIs(
ws.id,
read.id,
WorkspaceRole.Collaborator
),
(await models.workspaceUser.get(ws.id, read.id))?.type ===
WorkspaceRole.Collaborator,
'should be able to check permission'
);
t.truthy(
@@ -347,11 +338,8 @@ test('should be able to grant team member permission', async t => {
'should be able to grant permission'
);
t.true(
await permissions.tryCheckWorkspaceIs(
ws.id,
read.id,
WorkspaceRole.Admin
),
(await models.workspaceUser.get(ws.id, read.id))?.type ===
WorkspaceRole.Admin,
'should be able to check permission'
);
}
@@ -362,10 +350,9 @@ test('should be able to leave workspace', async t => {
const { owner, teamWorkspace: ws, admin, write, read } = await init(app);
app.switchUser(owner);
t.false(
await leaveWorkspace(app, ws.id),
'owner should not be able to leave workspace'
);
await t.throwsAsync(leaveWorkspace(app, ws.id), {
message: 'Owner can not leave the workspace.',
});
app.switchUser(admin);
t.true(
@@ -425,10 +412,9 @@ test('should be able to revoke team member', async t => {
'owner should be able to revoke member'
);
t.false(
await revokeUser(app, ws.id, owner.id),
'should not be able to revoke themselves'
);
await t.throwsAsync(revokeUser(app, ws.id, owner.id), {
message: 'You can not revoke your own permission.',
});
await revokeUser(app, ws.id, admin.id);
app.switchUser(admin);
@@ -508,7 +494,7 @@ test('should be able to approve team member', async t => {
const memberInvite = members.find(m => m.id === member.id)!;
t.is(memberInvite.status, 'UnderReview', 'should be under review');
t.is(await approveMember(app, tws.id, member.id), memberInvite.inviteId);
t.true(await approveMember(app, tws.id, member.id));
}
{
@@ -536,7 +522,7 @@ test('should be able to approve team member', async t => {
});
test('should be able to invite by link', async t => {
const { app, permissions, models } = t.context;
const { app, models } = t.context;
const {
createInviteLink,
owner,
@@ -562,10 +548,10 @@ test('should be able to invite by link', async t => {
// invite link
for (const [i] of Array.from({ length: 5 }).entries()) {
const user = await invite(`test${i}@affine.pro`);
const status = await permissions.getWorkspaceMemberStatus(ws.id, user.id);
const status = (await models.workspaceUser.get(ws.id, user.id))?.status;
t.is(
status,
WorkspaceMemberStatus.Accepted,
WorkspaceMemberStatus.UnderReview,
'should be able to check status'
);
}
@@ -587,12 +573,12 @@ test('should be able to invite by link', async t => {
const [m3, m4] = members;
t.is(
await permissions.getWorkspaceMemberStatus(tws.id, m3.id),
(await models.workspaceUser.get(tws.id, m3.id))?.status,
WorkspaceMemberStatus.NeedMoreSeatAndReview,
'should not change status'
);
t.is(
await permissions.getWorkspaceMemberStatus(tws.id, m4.id),
(await models.workspaceUser.get(tws.id, m4.id))?.status,
WorkspaceMemberStatus.NeedMoreSeatAndReview,
'should not change status'
);
@@ -600,14 +586,14 @@ test('should be able to invite by link', async t => {
models.workspaceFeature.add(tws.id, 'team_plan_v1', 'test', {
memberLimit: 6,
});
await permissions.refreshSeatStatus(tws.id, 6);
await models.workspaceUser.refresh(tws.id, 6);
t.is(
await permissions.getWorkspaceMemberStatus(tws.id, m3.id),
(await models.workspaceUser.get(tws.id, m3.id))?.status,
WorkspaceMemberStatus.UnderReview,
'should not change status'
);
t.is(
await permissions.getWorkspaceMemberStatus(tws.id, m4.id),
(await models.workspaceUser.get(tws.id, m4.id))?.status,
WorkspaceMemberStatus.NeedMoreSeatAndReview,
'should not change status'
);
@@ -615,9 +601,9 @@ test('should be able to invite by link', async t => {
models.workspaceFeature.add(tws.id, 'team_plan_v1', 'test', {
memberLimit: 7,
});
await permissions.refreshSeatStatus(tws.id, 7);
await models.workspaceUser.refresh(tws.id, 7);
t.is(
await permissions.getWorkspaceMemberStatus(tws.id, m4.id),
(await models.workspaceUser.get(tws.id, m4.id))?.status,
WorkspaceMemberStatus.UnderReview,
'should not change status'
);
@@ -665,6 +651,7 @@ test('should be able to emit events', async t => {
const { teamWorkspace: tws, owner, createInviteLink } = await init(app);
const [, invite] = await createInviteLink(tws);
const user = await invite('m3@affine.pro');
app.switchUser(owner);
const { members } = await getWorkspace(app, tws.id);
const memberInvite = members.find(m => m.id === user.id)!;
t.deepEqual(
@@ -698,7 +685,7 @@ test('should be able to emit events', async t => {
{
userId: read.id,
workspaceId: tws.id,
permission: WorkspaceRole.Admin,
role: WorkspaceRole.Admin,
},
],
'should emit role changed event'
@@ -712,7 +699,7 @@ test('should be able to emit events', async t => {
t.deepEqual(
ownershipTransferred,
[
'workspace.members.ownershipTransferred',
'workspace.owner.changed',
{ from: owner.id, to: read.id, workspaceId: tws.id },
],
'should emit owner transferred event'
@@ -880,20 +867,15 @@ test('should be able to grant and revoke doc user role', async t => {
grantDocUserRoles: true,
});
// external user now can manage the page
// external user can never be able to manage the page
{
app.switchUser(external);
const externalRes = await grantDocUserRoles(
app,
ws.id,
docId,
[read.id],
DocRole.Manager
await t.throwsAsync(
grantDocUserRoles(app, ws.id, docId, [read.id], DocRole.Manager),
{
message: `You do not have permission to perform Doc.Users.Manage action on doc ${docId}.`,
}
);
t.deepEqual(externalRes, {
grantDocUserRoles: true,
});
}
// revoke the role of the external user

View File

@@ -1,15 +1,24 @@
import { type Blob } from '@prisma/client';
import { TestingApp } from './testing-app';
export async function listBlobs(
app: TestingApp,
workspaceId: string
): Promise<string[]> {
): Promise<Blob[]> {
const res = await app.gql(`
query {
listBlobs(workspaceId: "${workspaceId}")
workspace(id: "${workspaceId}") {
blobs {
key
mime
size
createdAt
}
}
}
`);
return res.listBlobs;
return res.workspace.blobs;
}
export async function getWorkspaceBlobsSize(

View File

@@ -94,7 +94,7 @@ test('should visit public page', async t => {
const docs2 = await getWorkspacePublicDocs(app, workspace.id);
t.is(docs2.length, 0, 'failed to get shared docs');
await t.throwsAsync(revokePublicDoc(app, workspace.id, 'doc3'), {
message: 'Doc is not public',
message: 'Doc is not public.',
});
const docs3 = await getWorkspacePublicDocs(app, workspace.id);

View File

@@ -77,7 +77,7 @@ test('should list blobs', async t => {
const ret = await listBlobs(app, workspace.id);
t.is(ret.length, 2, 'failed to list blobs');
// list blob result is not ordered
t.deepEqual(ret.sort(), [hash1, hash2].sort());
t.deepEqual(ret.map(x => x.key).sort(), [hash1, hash2].sort());
});
test('should auto delete blobs when workspace is deleted', async t => {

View File

@@ -7,6 +7,7 @@ import Sinon from 'sinon';
import { PgWorkspaceDocStorageAdapter } from '../../core/doc';
import { WorkspaceBlobStorage } from '../../core/storage';
import { Models, WorkspaceRole } from '../../models';
import { createTestingApp, TestingApp, TestUser } from '../utils';
const test = ava as TestFn<{
@@ -15,6 +16,7 @@ const test = ava as TestFn<{
u1: TestUser;
storage: Sinon.SinonStubbedInstance<WorkspaceBlobStorage>;
workspace: Sinon.SinonStubbedInstance<PgWorkspaceDocStorageAdapter>;
models: Models;
}>;
test.before(async t => {
@@ -34,6 +36,7 @@ test.before(async t => {
t.context.app = app;
t.context.storage = app.get(WorkspaceBlobStorage);
t.context.workspace = app.get(PgWorkspaceDocStorageAdapter);
t.context.models = app.get(Models);
await db.workspaceDoc.create({
data: {
@@ -155,17 +158,14 @@ test('should not be able to get private workspace with no public pages', async t
});
test('should be able to get permission granted workspace', async t => {
const { app, db, storage } = t.context;
const { app, storage } = t.context;
await db.workspaceUserPermission.create({
data: {
workspaceId: 'totally-private',
userId: t.context.u1.id,
type: 1,
accepted: true,
status: WorkspaceMemberStatus.Accepted,
},
});
await t.context.models.workspaceUser.set(
'totally-private',
t.context.u1.id,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
storage.get.resolves(blob());
await app.login(t.context.u1);