mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
test(server): use new e2e (#11056)
This commit is contained in:
@@ -15,8 +15,8 @@
|
||||
"test:copilot": "ava \"src/__tests__/copilot-*.spec.ts\"",
|
||||
"test:coverage": "c8 ava --concurrency 1 --serial",
|
||||
"test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/copilot-*.spec.ts\"",
|
||||
"e2e": "cross-env TEST_MODE=e2e ava",
|
||||
"e2e:coverage": "cross-env TEST_MODE=e2e c8 ava",
|
||||
"e2e": "cross-env TEST_MODE=e2e ava --serial",
|
||||
"e2e:coverage": "cross-env TEST_MODE=e2e c8 ava --serial",
|
||||
"data-migration": "cross-env NODE_ENV=development r ./src/data/index.ts",
|
||||
"init": "yarn prisma migrate dev && yarn data-migration run",
|
||||
"seed": "r ./src/seed/index.ts",
|
||||
|
||||
@@ -33,3 +33,11 @@ e2e('should create workspace with owner', async t => {
|
||||
});
|
||||
t.truthy(workspace);
|
||||
});
|
||||
|
||||
e2e('should get current user', async t => {
|
||||
const user = await app.signup();
|
||||
await app.switchUser(user);
|
||||
const res = await app.gql({ query: getCurrentUserQuery });
|
||||
t.truthy(res.currentUser);
|
||||
t.is(res.currentUser!.id, user.id);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,696 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import {
|
||||
DocMode,
|
||||
listNotificationsQuery,
|
||||
MentionNotificationBodyType,
|
||||
mentionUserMutation,
|
||||
notificationCountQuery,
|
||||
NotificationObjectType,
|
||||
NotificationType,
|
||||
readNotificationMutation,
|
||||
} from '@affine/graphql';
|
||||
|
||||
import { Mockers } from '../../mocks';
|
||||
import { app, e2e } from '../test';
|
||||
|
||||
async function init() {
|
||||
const member = await app.create(Mockers.User);
|
||||
const owner = await app.create(Mockers.User);
|
||||
const workspace = await app.create(Mockers.Workspace, {
|
||||
owner: { id: owner.id },
|
||||
name: 'test-workspace-name',
|
||||
avatarKey: 'test-avatar-key',
|
||||
});
|
||||
|
||||
await app.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
});
|
||||
|
||||
return {
|
||||
member,
|
||||
owner,
|
||||
workspace,
|
||||
};
|
||||
}
|
||||
|
||||
e2e('should mention user in a doc', async t => {
|
||||
const { member, owner, workspace } = await init();
|
||||
|
||||
await app.login(owner);
|
||||
const { mentionUser: mentionId } = await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-1',
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
t.truthy(mentionId);
|
||||
// mention user at another doc
|
||||
const { mentionUser: mentionId2 } = await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-2',
|
||||
title: 'doc-title-2',
|
||||
elementId: 'element-id-2',
|
||||
mode: DocMode.edgeless,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
t.truthy(mentionId2);
|
||||
|
||||
await app.login(member);
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 10,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
const notifications = result.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
);
|
||||
t.is(notifications.length, 2);
|
||||
|
||||
const notification = notifications[1] as NotificationObjectType;
|
||||
t.is(notification.read, false);
|
||||
t.truthy(notification.createdAt);
|
||||
t.truthy(notification.updatedAt);
|
||||
const body = notification.body as MentionNotificationBodyType;
|
||||
t.is(body.workspace!.id, workspace.id);
|
||||
t.is(body.doc.id, 'doc-id-1');
|
||||
t.is(body.doc.title, 'doc-title-1');
|
||||
t.is(body.doc.blockId, 'block-id-1');
|
||||
t.is(body.doc.mode, DocMode.page);
|
||||
t.is(body.createdByUser!.id, owner.id);
|
||||
t.is(body.createdByUser!.name, owner.name);
|
||||
t.is(body.workspace!.id, workspace.id);
|
||||
t.is(body.workspace!.name, 'test-workspace-name');
|
||||
t.falsy(body.workspace!.avatarUrl);
|
||||
|
||||
const notification2 = notifications[0] as NotificationObjectType;
|
||||
t.is(notification2.read, false);
|
||||
t.truthy(notification2.createdAt);
|
||||
t.truthy(notification2.updatedAt);
|
||||
const body2 = notification2.body as MentionNotificationBodyType;
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.doc.id, 'doc-id-2');
|
||||
t.is(body2.doc.title, 'doc-title-2');
|
||||
t.is(body2.doc.elementId, 'element-id-2');
|
||||
t.is(body2.doc.mode, DocMode.edgeless);
|
||||
t.is(body2.createdByUser!.id, owner.id);
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.workspace!.name, 'test-workspace-name');
|
||||
t.falsy(body2.workspace!.avatarUrl);
|
||||
});
|
||||
|
||||
e2e('should mention doc mode support string value', async t => {
|
||||
const { member, owner, workspace } = await init();
|
||||
|
||||
await app.login(owner);
|
||||
const { mentionUser: mentionId } = await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-1',
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: 'page' as DocMode,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
t.truthy(mentionId);
|
||||
});
|
||||
|
||||
e2e('should throw error when mention user has no Doc.Read role', async t => {
|
||||
const { owner, workspace } = await init();
|
||||
const otherUser = await app.create(Mockers.User);
|
||||
|
||||
await app.login(owner);
|
||||
const docId = randomUUID();
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: otherUser.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: docId,
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: `Mentioned user can not access doc ${docId}.`,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should throw error when mention a not exists user', async t => {
|
||||
const { owner, workspace } = await init();
|
||||
|
||||
await app.login(owner);
|
||||
const docId = randomUUID();
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: 'user-id-not-exists',
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: docId,
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: `Mentioned user can not access doc ${docId}.`,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should not mention user oneself', async t => {
|
||||
const { owner, workspace } = await init();
|
||||
|
||||
await app.login(owner);
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: owner.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-1',
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: 'You can not mention yourself.',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should mark notification as read', async t => {
|
||||
const { member, owner, workspace } = await init();
|
||||
|
||||
await app.login(owner);
|
||||
const { mentionUser: mentionId } = await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-1',
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
t.truthy(mentionId);
|
||||
|
||||
await app.login(member);
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 10,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
t.truthy(mentionId);
|
||||
|
||||
const notifications = result.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
);
|
||||
|
||||
for (const notification of notifications) {
|
||||
t.is(notification.read, false);
|
||||
await app.gql({
|
||||
query: readNotificationMutation,
|
||||
variables: {
|
||||
id: notification.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
const count = await app.gql({
|
||||
query: notificationCountQuery,
|
||||
});
|
||||
t.is(count.currentUser!.notificationCount, 0);
|
||||
|
||||
// read again should work
|
||||
for (const notification of notifications) {
|
||||
t.is(notification.read, false);
|
||||
await app.gql({
|
||||
query: readNotificationMutation,
|
||||
variables: {
|
||||
id: notification.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
e2e('should throw error when read the other user notification', async t => {
|
||||
const { member, owner, workspace } = await init();
|
||||
|
||||
await app.login(owner);
|
||||
const { mentionUser: mentionId } = await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-1',
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
t.truthy(mentionId);
|
||||
|
||||
await app.login(member);
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 10,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
const notifications = result.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
);
|
||||
t.is(notifications[0].read, false);
|
||||
|
||||
await app.login(owner);
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: readNotificationMutation,
|
||||
variables: {
|
||||
id: notifications[0].id,
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: 'Notification not found.',
|
||||
}
|
||||
);
|
||||
// notification not exists
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: readNotificationMutation,
|
||||
variables: {
|
||||
id: 'notification-id-not-exists',
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: 'Notification not found.',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should throw error when mention call with invalid params', async t => {
|
||||
await app.signup();
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: '1',
|
||||
workspaceId: '1',
|
||||
doc: {
|
||||
id: '1',
|
||||
title: 'doc-title-1'.repeat(100),
|
||||
blockId: '1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: /Validation error/,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should throw error when mention mode value is invalid', async t => {
|
||||
await app.signup();
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: randomUUID(),
|
||||
workspaceId: randomUUID(),
|
||||
doc: {
|
||||
id: randomUUID(),
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: 'invalid-mode' as DocMode,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
message:
|
||||
'Variable "$input" got invalid value "invalid-mode" at "input.doc.mode"; Value "invalid-mode" does not exist in "DocMode" enum.',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should get empty notifications', async t => {
|
||||
await app.signup();
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 10,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
const notifications = result.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
);
|
||||
t.is(notifications.length, 0);
|
||||
t.is(result.currentUser!.notifications.totalCount, 0);
|
||||
});
|
||||
|
||||
e2e('should list and count notifications', async t => {
|
||||
const { member, owner, workspace } = await init();
|
||||
await app.login(owner);
|
||||
await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-1',
|
||||
title: 'doc-title-1',
|
||||
blockId: 'block-id-1',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-2',
|
||||
title: 'doc-title-2',
|
||||
blockId: 'block-id-2',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-3',
|
||||
title: 'doc-title-3',
|
||||
blockId: 'block-id-3',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// mention user in another workspace
|
||||
const workspace2 = await app.create(Mockers.Workspace, {
|
||||
owner: { id: owner.id },
|
||||
name: 'test-workspace-name2',
|
||||
avatarKey: 'test-avatar-key2',
|
||||
});
|
||||
await app.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace2.id,
|
||||
userId: member.id,
|
||||
});
|
||||
await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace2.id,
|
||||
doc: {
|
||||
id: 'doc-id-4',
|
||||
title: 'doc-title-4',
|
||||
blockId: 'block-id-4',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
{
|
||||
await app.login(member);
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 10,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
const notifications = result.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
) as NotificationObjectType[];
|
||||
t.is(notifications.length, 4);
|
||||
t.is(result.currentUser!.notifications.totalCount, 4);
|
||||
|
||||
const notification = notifications[0];
|
||||
t.is(notification.read, false);
|
||||
const body = notification.body as MentionNotificationBodyType;
|
||||
t.is(body.type, NotificationType.Mention);
|
||||
t.is(body.workspace!.name, 'test-workspace-name2');
|
||||
t.is(body.workspace!.id, workspace2.id);
|
||||
t.falsy(body.workspace!.avatarUrl);
|
||||
t.is(body.doc.id, 'doc-id-4');
|
||||
t.is(body.doc.title, 'doc-title-4');
|
||||
t.is(body.doc.blockId, 'block-id-4');
|
||||
t.is(body.createdByUser!.id, owner.id);
|
||||
|
||||
const notification2 = notifications[1];
|
||||
t.is(notification2.read, false);
|
||||
const body2 = notification2.body as MentionNotificationBodyType;
|
||||
t.is(body2.type, NotificationType.Mention);
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.doc.id, 'doc-id-3');
|
||||
t.is(body2.doc.title, 'doc-title-3');
|
||||
t.is(body2.doc.blockId, 'block-id-3');
|
||||
t.is(body2.createdByUser!.id, owner.id);
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.workspace!.name, 'test-workspace-name');
|
||||
t.falsy(body2.workspace!.avatarUrl);
|
||||
}
|
||||
|
||||
{
|
||||
await app.login(member);
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 10,
|
||||
offset: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
t.is(result.currentUser!.notifications.totalCount, 4);
|
||||
t.is(result.currentUser!.notifications.pageInfo.hasNextPage, false);
|
||||
t.is(result.currentUser!.notifications.pageInfo.hasPreviousPage, true);
|
||||
const notifications = result.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
) as NotificationObjectType[];
|
||||
t.is(notifications.length, 2);
|
||||
|
||||
const notification = notifications[0];
|
||||
t.is(notification.read, false);
|
||||
const body = notification.body as MentionNotificationBodyType;
|
||||
t.is(body.workspace!.id, workspace.id);
|
||||
t.is(body.doc.id, 'doc-id-2');
|
||||
t.is(body.doc.title, 'doc-title-2');
|
||||
t.is(body.doc.blockId, 'block-id-2');
|
||||
t.is(body.createdByUser!.id, owner.id);
|
||||
t.is(body.workspace!.id, workspace.id);
|
||||
t.is(body.workspace!.name, 'test-workspace-name');
|
||||
t.falsy(body.workspace!.avatarUrl);
|
||||
|
||||
const notification2 = notifications[1];
|
||||
t.is(notification2.read, false);
|
||||
const body2 = notification2.body as MentionNotificationBodyType;
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.doc.id, 'doc-id-1');
|
||||
t.is(body2.doc.title, 'doc-title-1');
|
||||
t.is(body2.doc.blockId, 'block-id-1');
|
||||
t.is(body2.createdByUser!.id, owner.id);
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.workspace!.name, 'test-workspace-name');
|
||||
t.falsy(body2.workspace!.avatarUrl);
|
||||
}
|
||||
|
||||
{
|
||||
await app.login(member);
|
||||
const result = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 2,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
t.is(result.currentUser!.notifications.totalCount, 4);
|
||||
t.is(result.currentUser!.notifications.pageInfo.hasNextPage, true);
|
||||
t.is(result.currentUser!.notifications.pageInfo.hasPreviousPage, false);
|
||||
const notifications = result.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
) as NotificationObjectType[];
|
||||
t.is(notifications.length, 2);
|
||||
|
||||
const notification = notifications[0];
|
||||
t.is(notification.read, false);
|
||||
const body = notification.body as MentionNotificationBodyType;
|
||||
t.is(body.workspace!.id, workspace2.id);
|
||||
t.is(body.doc.id, 'doc-id-4');
|
||||
t.is(body.doc.title, 'doc-title-4');
|
||||
t.is(body.doc.blockId, 'block-id-4');
|
||||
t.is(body.createdByUser!.id, owner.id);
|
||||
t.is(body.workspace!.id, workspace2.id);
|
||||
t.is(body.workspace!.name, 'test-workspace-name2');
|
||||
t.falsy(body.workspace!.avatarUrl);
|
||||
t.is(
|
||||
notification.createdAt.toString(),
|
||||
Buffer.from(
|
||||
result.currentUser!.notifications.pageInfo.startCursor!,
|
||||
'base64'
|
||||
).toString('utf-8')
|
||||
);
|
||||
const notification2 = notifications[1];
|
||||
t.is(notification2.read, false);
|
||||
const body2 = notification2.body as MentionNotificationBodyType;
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.doc.id, 'doc-id-3');
|
||||
t.is(body2.doc.title, 'doc-title-3');
|
||||
t.is(body2.doc.blockId, 'block-id-3');
|
||||
t.is(body2.createdByUser!.id, owner.id);
|
||||
t.is(body2.workspace!.id, workspace.id);
|
||||
t.is(body2.workspace!.name, 'test-workspace-name');
|
||||
t.falsy(body2.workspace!.avatarUrl);
|
||||
|
||||
await app.login(owner);
|
||||
await app.gql({
|
||||
query: mentionUserMutation,
|
||||
variables: {
|
||||
input: {
|
||||
userId: member.id,
|
||||
workspaceId: workspace.id,
|
||||
doc: {
|
||||
id: 'doc-id-5',
|
||||
title: 'doc-title-5',
|
||||
blockId: 'block-id-5',
|
||||
mode: DocMode.page,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// get new notifications
|
||||
await app.login(member);
|
||||
const result2 = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 2,
|
||||
offset: 0,
|
||||
after: result.currentUser!.notifications.pageInfo.startCursor,
|
||||
},
|
||||
},
|
||||
});
|
||||
t.is(result2.currentUser!.notifications.totalCount, 5);
|
||||
t.is(result2.currentUser!.notifications.pageInfo.hasNextPage, false);
|
||||
t.is(result2.currentUser!.notifications.pageInfo.hasPreviousPage, true);
|
||||
const notifications2 = result2.currentUser!.notifications.edges.map(
|
||||
edge => edge.node
|
||||
) as NotificationObjectType[];
|
||||
t.is(notifications2.length, 1);
|
||||
|
||||
const notification3 = notifications2[0];
|
||||
t.is(notification3.read, false);
|
||||
const body3 = notification3.body as MentionNotificationBodyType;
|
||||
t.is(body3.workspace!.id, workspace.id);
|
||||
t.is(body3.doc.id, 'doc-id-5');
|
||||
t.is(body3.doc.title, 'doc-title-5');
|
||||
t.is(body3.doc.blockId, 'block-id-5');
|
||||
t.is(body3.createdByUser!.id, owner.id);
|
||||
t.is(body3.createdByUser!.name, owner.name);
|
||||
t.is(body3.workspace!.id, workspace.id);
|
||||
t.is(body3.workspace!.name, 'test-workspace-name');
|
||||
t.falsy(body3.workspace!.avatarUrl);
|
||||
|
||||
// no new notifications
|
||||
const result3 = await app.gql({
|
||||
query: listNotificationsQuery,
|
||||
variables: {
|
||||
pagination: {
|
||||
first: 2,
|
||||
offset: 0,
|
||||
after: result2.currentUser!.notifications.pageInfo.startCursor,
|
||||
},
|
||||
},
|
||||
});
|
||||
t.is(result3.currentUser!.notifications.totalCount, 5);
|
||||
t.is(result3.currentUser!.notifications.pageInfo.hasNextPage, false);
|
||||
t.is(result3.currentUser!.notifications.pageInfo.hasPreviousPage, true);
|
||||
t.is(result3.currentUser!.notifications.pageInfo.startCursor, null);
|
||||
t.is(result3.currentUser!.notifications.pageInfo.endCursor, null);
|
||||
t.is(result3.currentUser!.notifications.edges.length, 0);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,314 @@
|
||||
import {
|
||||
acceptInviteByInviteIdMutation,
|
||||
getMembersByWorkspaceIdQuery,
|
||||
inviteByEmailMutation,
|
||||
leaveWorkspaceMutation,
|
||||
revokeMemberPermissionMutation,
|
||||
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);
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
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);
|
||||
|
||||
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.allSettled(
|
||||
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);
|
||||
});
|
||||
@@ -2,17 +2,20 @@ export { createFactory } from './factory';
|
||||
export * from './team-workspace.mock';
|
||||
export * from './user.mock';
|
||||
export * from './workspace.mock';
|
||||
export * from './workspace-user.mock';
|
||||
|
||||
import { MockMailer } from './mailer.mock';
|
||||
import { MockJobQueue } from './queue.mock';
|
||||
import { MockTeamWorkspace } from './team-workspace.mock';
|
||||
import { MockUser } from './user.mock';
|
||||
import { MockWorkspace } from './workspace.mock';
|
||||
import { MockWorkspaceUser } from './workspace-user.mock';
|
||||
|
||||
export const Mockers = {
|
||||
User: MockUser,
|
||||
Workspace: MockWorkspace,
|
||||
TeamWorkspace: MockTeamWorkspace,
|
||||
WorkspaceUser: MockWorkspaceUser,
|
||||
};
|
||||
|
||||
export { MockJobQueue, MockMailer };
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Prisma, WorkspaceUserRole } from '@prisma/client';
|
||||
|
||||
import { WorkspaceMemberStatus, WorkspaceRole } from '../../models';
|
||||
import { Mocker } from './factory';
|
||||
|
||||
export type MockWorkspaceUserInput = Omit<
|
||||
Prisma.WorkspaceUserRoleUncheckedCreateInput,
|
||||
'type'
|
||||
> & {
|
||||
type?: WorkspaceRole;
|
||||
};
|
||||
|
||||
export class MockWorkspaceUser extends Mocker<
|
||||
MockWorkspaceUserInput,
|
||||
WorkspaceUserRole
|
||||
> {
|
||||
override async create(input: MockWorkspaceUserInput) {
|
||||
return await this.db.workspaceUserRole.create({
|
||||
data: {
|
||||
type: WorkspaceRole.Collaborator,
|
||||
status: WorkspaceMemberStatus.Accepted,
|
||||
...input,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user