AFFiNE/packages/backend/server/tests/workspace.e2e.ts

292 lines
8.6 KiB
TypeScript

import type { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { type TestFn } from 'ava';
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
import request from 'supertest';
import { AppModule } from '../src/app';
import { RevertCommand, RunCommand } from '../src/data/commands/run';
import {
acceptInviteById,
createWorkspace,
currentUser,
getPublicWorkspace,
getWorkspacePublicPages,
initFeatureConfigs,
inviteUser,
publishPage,
revokePublicPage,
signUp,
updateWorkspace,
} from './utils';
const test = ava as TestFn<{
app: INestApplication;
client: PrismaClient;
}>;
test.beforeEach(async t => {
const client = new PrismaClient();
await client.$connect();
await client.user.deleteMany({});
await client.update.deleteMany({});
await client.snapshot.deleteMany({});
await client.workspace.deleteMany({});
await client.$disconnect();
const module = await Test.createTestingModule({
imports: [AppModule],
providers: [RevertCommand, RunCommand],
}).compile();
const app = module.createNestApplication();
app.use(
graphqlUploadExpress({
maxFileSize: 10 * 1024 * 1024,
maxFiles: 5,
})
);
await app.init();
t.context.client = client;
t.context.app = app;
// init features
await initFeatureConfigs(module);
});
test.afterEach.always(async t => {
await t.context.app.close();
});
test('should register a user', async t => {
const user = await signUp(t.context.app, 'u1', 'u1@affine.pro', '123456');
t.is(typeof user.id, 'string', 'user.id is not a string');
t.is(user.name, 'u1', 'user.name is not valid');
t.is(user.email, 'u1@affine.pro', 'user.email is not valid');
});
test.skip('should be throttled at call signUp', async t => {
const { app } = t.context;
let token = '';
for (let i = 0; i < 10; i++) {
token = (await signUp(app, `u${i}`, `u${i}@affine.pro`, `${i}`)).token
.token;
// throttles are applied to each endpoint separately
await currentUser(app, token);
}
await t.throwsAsync(() => signUp(app, 'u11', 'u11@affine.pro', '11'));
await t.throwsAsync(() => currentUser(app, token));
});
test('should create a workspace', async t => {
const { app } = t.context;
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
const workspace = await createWorkspace(app, user.token.token);
t.is(typeof workspace.id, 'string', 'workspace.id is not a string');
});
test('should can publish workspace', async t => {
const { app } = t.context;
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
const workspace = await createWorkspace(app, user.token.token);
const isPublic = await updateWorkspace(
app,
user.token.token,
workspace.id,
true
);
t.true(isPublic, 'failed to publish workspace');
const isPrivate = await updateWorkspace(
app,
user.token.token,
workspace.id,
false
);
t.false(isPrivate, 'failed to unpublish workspace');
});
test('should can read published workspace', async t => {
const { app } = t.context;
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
const workspace = await createWorkspace(app, user.token.token);
await t.throwsAsync(() => getPublicWorkspace(app, 'not_exists_ws'));
await t.throwsAsync(() => getPublicWorkspace(app, workspace.id));
await updateWorkspace(app, user.token.token, workspace.id, true);
const publicWorkspace = await getPublicWorkspace(app, workspace.id);
t.is(publicWorkspace.id, workspace.id, 'failed to get public workspace');
});
test('should share a page', 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', '1');
const workspace = await createWorkspace(app, u1.token.token);
const share = await publishPage(app, u1.token.token, workspace.id, 'page1');
t.is(share.id, 'page1', 'failed to share page');
const pages = await getWorkspacePublicPages(
app,
u1.token.token,
workspace.id
);
t.is(pages.length, 1, 'failed to get shared pages');
t.deepEqual(
pages[0],
{ id: 'page1', mode: 'Page' },
'failed to get shared page: page1'
);
const resp1 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.auth(u1.token.token, { type: 'bearer' });
t.is(resp1.statusCode, 200, 'failed to get root doc with u1 token');
const resp2 = await request(app.getHttpServer()).get(
`/api/workspaces/${workspace.id}/docs/${workspace.id}`
);
t.is(resp2.statusCode, 200, 'failed to get root doc with public pages');
const resp3 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/page1`)
.auth(u1.token.token, { type: 'bearer' });
// 404 because we don't put the page doc to server
t.is(resp3.statusCode, 404, 'failed to get shared doc with u1 token');
const resp4 = await request(app.getHttpServer()).get(
`/api/workspaces/${workspace.id}/docs/page1`
);
// 404 because we don't put the page doc to server
t.is(resp4.statusCode, 404, 'should not get shared doc without token');
const msg1 = await publishPage(app, u2.token.token, 'not_exists_ws', 'page2');
t.is(msg1, 'Permission denied', 'unauthorized user can share page');
const msg2 = await revokePublicPage(
app,
u2.token.token,
'not_exists_ws',
'page2'
);
t.is(msg2, 'Permission denied', 'unauthorized user can share page');
await acceptInviteById(
app,
workspace.id,
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin')
);
const invited = await publishPage(app, u2.token.token, workspace.id, 'page2');
t.is(invited.id, 'page2', 'failed to share page');
const revoke = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page1'
);
t.false(revoke.public, 'failed to revoke page');
const pages2 = await getWorkspacePublicPages(
app,
u1.token.token,
workspace.id
);
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(
app,
u1.token.token,
workspace.id,
'page3'
);
t.is(msg3, 'Page is not public');
const msg4 = await revokePublicPage(
app,
u1.token.token,
workspace.id,
'page2'
);
t.false(msg4.public, 'failed to revoke page');
const page3 = await getWorkspacePublicPages(
app,
u1.token.token,
workspace.id
);
t.is(page3.length, 0, 'failed to get shared pages');
});
test('should can 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');
const workspace = await createWorkspace(app, u1.token.token);
const res1 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.auth(u1.token.token, { type: 'bearer' })
.expect(200)
.type('application/octet-stream');
t.deepEqual(
res1.body,
Buffer.from([0, 0]),
'failed to get doc with u1 token'
);
await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.expect(403);
await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.auth(u2.token.token, { type: 'bearer' })
.expect(403);
await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.auth(u2.token.token, { type: 'bearer' })
.expect(403);
await acceptInviteById(
app,
workspace.id,
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin')
);
const res2 = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.auth(u2.token.token, { type: 'bearer' })
.expect(200)
.type('application/octet-stream');
t.deepEqual(
res2.body,
Buffer.from([0, 0]),
'failed to get doc with u2 token'
);
});
test('should be able to get public workspace doc', async t => {
const { app } = t.context;
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
const workspace = await createWorkspace(app, user.token.token);
const isPublic = await updateWorkspace(
app,
user.token.token,
workspace.id,
true
);
t.true(isPublic, 'failed to publish workspace');
const res = await request(app.getHttpServer())
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
.expect(200)
.type('application/octet-stream');
t.deepEqual(res.body, Buffer.from([0, 0]), 'failed to get public doc');
});