mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-26 02:35:58 +08: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:copilot": "ava \"src/__tests__/copilot-*.spec.ts\"",
|
||||||
"test:coverage": "c8 ava --concurrency 1 --serial",
|
"test:coverage": "c8 ava --concurrency 1 --serial",
|
||||||
"test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/copilot-*.spec.ts\"",
|
"test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/copilot-*.spec.ts\"",
|
||||||
"e2e": "cross-env TEST_MODE=e2e ava",
|
"e2e": "cross-env TEST_MODE=e2e ava --serial",
|
||||||
"e2e:coverage": "cross-env TEST_MODE=e2e c8 ava",
|
"e2e:coverage": "cross-env TEST_MODE=e2e c8 ava --serial",
|
||||||
"data-migration": "cross-env NODE_ENV=development r ./src/data/index.ts",
|
"data-migration": "cross-env NODE_ENV=development r ./src/data/index.ts",
|
||||||
"init": "yarn prisma migrate dev && yarn data-migration run",
|
"init": "yarn prisma migrate dev && yarn data-migration run",
|
||||||
"seed": "r ./src/seed/index.ts",
|
"seed": "r ./src/seed/index.ts",
|
||||||
|
|||||||
@@ -33,3 +33,11 @@ e2e('should create workspace with owner', async t => {
|
|||||||
});
|
});
|
||||||
t.truthy(workspace);
|
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 './team-workspace.mock';
|
||||||
export * from './user.mock';
|
export * from './user.mock';
|
||||||
export * from './workspace.mock';
|
export * from './workspace.mock';
|
||||||
|
export * from './workspace-user.mock';
|
||||||
|
|
||||||
import { MockMailer } from './mailer.mock';
|
import { MockMailer } from './mailer.mock';
|
||||||
import { MockJobQueue } from './queue.mock';
|
import { MockJobQueue } from './queue.mock';
|
||||||
import { MockTeamWorkspace } from './team-workspace.mock';
|
import { MockTeamWorkspace } from './team-workspace.mock';
|
||||||
import { MockUser } from './user.mock';
|
import { MockUser } from './user.mock';
|
||||||
import { MockWorkspace } from './workspace.mock';
|
import { MockWorkspace } from './workspace.mock';
|
||||||
|
import { MockWorkspaceUser } from './workspace-user.mock';
|
||||||
|
|
||||||
export const Mockers = {
|
export const Mockers = {
|
||||||
User: MockUser,
|
User: MockUser,
|
||||||
Workspace: MockWorkspace,
|
Workspace: MockWorkspace,
|
||||||
TeamWorkspace: MockTeamWorkspace,
|
TeamWorkspace: MockTeamWorkspace,
|
||||||
|
WorkspaceUser: MockWorkspaceUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { MockJobQueue, MockMailer };
|
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