mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
fix(server): send invitation review notification (#11203)
This commit is contained in:
@@ -8,6 +8,7 @@ import { MockMailer } from './mailer.mock';
|
||||
import { MockJobQueue } from './queue.mock';
|
||||
import { MockTeamWorkspace } from './team-workspace.mock';
|
||||
import { MockUser } from './user.mock';
|
||||
import { MockUserSettings } from './user-settings.mock';
|
||||
import { MockWorkspace } from './workspace.mock';
|
||||
import { MockWorkspaceUser } from './workspace-user.mock';
|
||||
|
||||
@@ -16,6 +17,7 @@ export const Mockers = {
|
||||
Workspace: MockWorkspace,
|
||||
TeamWorkspace: MockTeamWorkspace,
|
||||
WorkspaceUser: MockWorkspaceUser,
|
||||
UserSettings: MockUserSettings,
|
||||
};
|
||||
|
||||
export { MockJobQueue, MockMailer };
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { UserSettings } from '@prisma/client';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
import { UserSettingsInput } from '../../models';
|
||||
import { Mocker } from './factory';
|
||||
|
||||
export type MockUserSettingsInput = UserSettingsInput & {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export type MockedUserSettings = UserSettings;
|
||||
|
||||
export class MockUserSettings extends Mocker<
|
||||
MockUserSettingsInput,
|
||||
MockedUserSettings
|
||||
> {
|
||||
override async create(input: MockUserSettingsInput) {
|
||||
return await this.db.userSettings.create({
|
||||
data: {
|
||||
userId: input.userId,
|
||||
payload: {
|
||||
...omit(input, 'userId'),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { mock } from 'node:test';
|
||||
|
||||
import ava, { TestFn } from 'ava';
|
||||
|
||||
import { Mockers } from '../../../__tests__/mocks';
|
||||
import {
|
||||
createTestingModule,
|
||||
type TestingModule,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
NotificationType,
|
||||
User,
|
||||
Workspace,
|
||||
WorkspaceMemberStatus,
|
||||
} from '../../../models';
|
||||
import { DocReader } from '../../doc';
|
||||
import { NotificationService } from '../service';
|
||||
@@ -41,15 +43,14 @@ let member: User;
|
||||
let workspace: Workspace;
|
||||
|
||||
test.beforeEach(async t => {
|
||||
await t.context.module.initTestingDB();
|
||||
owner = await t.context.models.user.create({
|
||||
email: `${randomUUID()}@affine.pro`,
|
||||
});
|
||||
member = await t.context.models.user.create({
|
||||
email: `${randomUUID()}@affine.pro`,
|
||||
});
|
||||
workspace = await t.context.models.workspace.create(owner.id);
|
||||
await t.context.models.workspace.update(workspace.id, {
|
||||
const { module } = t.context;
|
||||
await module.initTestingDB();
|
||||
owner = await module.create(Mockers.User);
|
||||
member = await module.create(Mockers.User);
|
||||
workspace = await module.create(Mockers.Workspace, {
|
||||
owner: {
|
||||
id: owner.id,
|
||||
},
|
||||
name: 'Test Workspace',
|
||||
avatarKey: 'test-avatar-key',
|
||||
});
|
||||
@@ -87,12 +88,13 @@ test('should create invitation notification and email', async t => {
|
||||
});
|
||||
|
||||
test('should not send invitation email if user setting is not to receive invitation email', async t => {
|
||||
const { notificationService } = t.context;
|
||||
const { notificationService, module } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
await t.context.models.userSettings.set(member.id, {
|
||||
await module.create(Mockers.UserSettings, {
|
||||
userId: member.id,
|
||||
receiveInvitationEmail: false,
|
||||
});
|
||||
const invitationMailCount = t.context.module.mails.count('MemberInvitation');
|
||||
const invitationMailCount = module.mails.count('MemberInvitation');
|
||||
const notification = await notificationService.createInvitation({
|
||||
userId: member.id,
|
||||
body: {
|
||||
@@ -107,11 +109,11 @@ test('should not send invitation email if user setting is not to receive invitat
|
||||
});
|
||||
|
||||
test('should not create invitation notification if user is already a member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
mock.method(models.workspaceUser, 'getActive', async () => ({
|
||||
id: inviteId,
|
||||
}));
|
||||
const { notificationService, module } = t.context;
|
||||
const { id: inviteId } = await module.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
});
|
||||
const notification = await notificationService.createInvitation({
|
||||
userId: member.id,
|
||||
body: {
|
||||
@@ -124,8 +126,11 @@ test('should not create invitation notification if user is already a member', as
|
||||
});
|
||||
|
||||
test('should create invitation accepted notification and email', async t => {
|
||||
const { notificationService } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
const { notificationService, module } = t.context;
|
||||
const { id: inviteId } = await module.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
});
|
||||
const notification = await notificationService.createInvitationAccepted({
|
||||
userId: owner.id,
|
||||
body: {
|
||||
@@ -142,15 +147,19 @@ test('should create invitation accepted notification and email', async t => {
|
||||
t.is(notification!.body.inviteId, inviteId);
|
||||
|
||||
// should send email
|
||||
const invitationAcceptedMail = t.context.module.mails.last('MemberAccepted');
|
||||
const invitationAcceptedMail = module.mails.last('MemberAccepted');
|
||||
t.is(invitationAcceptedMail.to, owner.email);
|
||||
});
|
||||
|
||||
test('should not send invitation accepted email if user settings is not receive invitation email', async t => {
|
||||
const { notificationService } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
const { notificationService, module } = t.context;
|
||||
const { id: inviteId } = await module.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
});
|
||||
// should not send email if user settings is not receive invitation email
|
||||
await t.context.models.userSettings.set(owner.id, {
|
||||
await module.create(Mockers.UserSettings, {
|
||||
userId: owner.id,
|
||||
receiveInvitationEmail: false,
|
||||
});
|
||||
const invitationAcceptedMailCount =
|
||||
@@ -172,9 +181,8 @@ test('should not send invitation accepted email if user settings is not receive
|
||||
});
|
||||
|
||||
test('should not create invitation accepted notification if user is not an active member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
const { notificationService } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
mock.method(models.workspaceUser, 'getActive', async () => null);
|
||||
const notification = await notificationService.createInvitationAccepted({
|
||||
userId: owner.id,
|
||||
body: {
|
||||
@@ -225,9 +233,8 @@ test('should create invitation rejected notification', async t => {
|
||||
});
|
||||
|
||||
test('should create invitation review request notification if user is not an active member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
const { notificationService, module } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
mock.method(models.workspaceUser, 'getActive', async () => null);
|
||||
const notification = await notificationService.createInvitationReviewRequest({
|
||||
userId: owner.id,
|
||||
body: {
|
||||
@@ -244,18 +251,18 @@ test('should create invitation review request notification if user is not an act
|
||||
t.is(notification!.body.inviteId, inviteId);
|
||||
|
||||
// should send email
|
||||
const invitationReviewRequestMail = t.context.module.mails.last(
|
||||
const invitationReviewRequestMail = module.mails.last(
|
||||
'LinkInvitationReviewRequest'
|
||||
);
|
||||
t.is(invitationReviewRequestMail.to, owner.email);
|
||||
});
|
||||
|
||||
test('should not create invitation review request notification if user is an active member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
mock.method(models.workspaceUser, 'getActive', async () => ({
|
||||
id: inviteId,
|
||||
}));
|
||||
const { notificationService, module } = t.context;
|
||||
const { id: inviteId } = await module.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
});
|
||||
const notification = await notificationService.createInvitationReviewRequest({
|
||||
userId: owner.id,
|
||||
body: {
|
||||
@@ -268,11 +275,11 @@ test('should not create invitation review request notification if user is an act
|
||||
});
|
||||
|
||||
test('should create invitation review approved notification if user is an active member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
mock.method(models.workspaceUser, 'getActive', async () => ({
|
||||
id: inviteId,
|
||||
}));
|
||||
const { notificationService, module } = t.context;
|
||||
const { id: inviteId } = await module.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
});
|
||||
const notification = await notificationService.createInvitationReviewApproved(
|
||||
{
|
||||
userId: member.id,
|
||||
@@ -298,15 +305,18 @@ test('should create invitation review approved notification if user is an active
|
||||
});
|
||||
|
||||
test('should not create invitation review approved notification if user is not an active member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
mock.method(models.workspaceUser, 'getActive', async () => null);
|
||||
const { notificationService, module } = t.context;
|
||||
const { id: inviteId } = await module.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
status: WorkspaceMemberStatus.Pending,
|
||||
});
|
||||
const notification = await notificationService.createInvitationReviewApproved(
|
||||
{
|
||||
userId: owner.id,
|
||||
userId: member.id,
|
||||
body: {
|
||||
workspaceId: workspace.id,
|
||||
createdByUserId: member.id,
|
||||
createdByUserId: owner.id,
|
||||
inviteId,
|
||||
},
|
||||
}
|
||||
@@ -315,8 +325,7 @@ test('should not create invitation review approved notification if user is not a
|
||||
});
|
||||
|
||||
test('should create invitation review declined notification if user is not an active member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
mock.method(models.workspaceUser, 'getActive', async () => null);
|
||||
const { notificationService, module } = t.context;
|
||||
const notification = await notificationService.createInvitationReviewDeclined(
|
||||
{
|
||||
userId: member.id,
|
||||
@@ -333,18 +342,18 @@ test('should create invitation review declined notification if user is not an ac
|
||||
t.is(notification!.body.createdByUserId, owner.id);
|
||||
|
||||
// should send email
|
||||
const invitationReviewDeclinedMail = t.context.module.mails.last(
|
||||
const invitationReviewDeclinedMail = module.mails.last(
|
||||
'LinkInvitationDecline'
|
||||
);
|
||||
t.is(invitationReviewDeclinedMail.to, member.email);
|
||||
});
|
||||
|
||||
test('should not create invitation review declined notification if user is an active member', async t => {
|
||||
const { notificationService, models } = t.context;
|
||||
const inviteId = randomUUID();
|
||||
mock.method(models.workspaceUser, 'getActive', async () => ({
|
||||
id: inviteId,
|
||||
}));
|
||||
const { notificationService, module } = t.context;
|
||||
await module.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
});
|
||||
const notification = await notificationService.createInvitationReviewDeclined(
|
||||
{
|
||||
userId: owner.id,
|
||||
@@ -413,7 +422,7 @@ test('should throw error on mark notification as read if notification is not fou
|
||||
});
|
||||
|
||||
test('should throw error on mark notification as read if notification user is not the same', async t => {
|
||||
const { notificationService } = t.context;
|
||||
const { notificationService, module } = t.context;
|
||||
const notification = await notificationService.createInvitation({
|
||||
userId: member.id,
|
||||
body: {
|
||||
@@ -422,9 +431,7 @@ test('should throw error on mark notification as read if notification user is no
|
||||
inviteId: randomUUID(),
|
||||
},
|
||||
});
|
||||
const otherUser = await t.context.models.user.create({
|
||||
email: `${randomUUID()}@affine.pro`,
|
||||
});
|
||||
const otherUser = await module.create(Mockers.User);
|
||||
await t.throwsAsync(
|
||||
notificationService.markAsRead(otherUser.id, notification!.id),
|
||||
{
|
||||
@@ -538,7 +545,7 @@ test('should raw doc title in mention notification if no doc found', async t =>
|
||||
});
|
||||
|
||||
test('should send mention email by user setting', async t => {
|
||||
const { notificationService } = t.context;
|
||||
const { notificationService, module } = t.context;
|
||||
const docId = randomUUID();
|
||||
const notification = await notificationService.createMention({
|
||||
userId: member.id,
|
||||
@@ -555,12 +562,13 @@ test('should send mention email by user setting', async t => {
|
||||
});
|
||||
t.truthy(notification);
|
||||
// should send mention email
|
||||
const mentionMail = t.context.module.mails.last('Mention');
|
||||
const mentionMail = module.mails.last('Mention');
|
||||
t.is(mentionMail.to, member.email);
|
||||
|
||||
// update user setting to not receive mention email
|
||||
const mentionMailCount = t.context.module.mails.count('Mention');
|
||||
await t.context.models.userSettings.set(member.id, {
|
||||
const mentionMailCount = module.mails.count('Mention');
|
||||
await module.create(Mockers.UserSettings, {
|
||||
userId: member.id,
|
||||
receiveMentionEmail: false,
|
||||
});
|
||||
await notificationService.createMention({
|
||||
@@ -577,5 +585,5 @@ test('should send mention email by user setting', async t => {
|
||||
},
|
||||
});
|
||||
// should not send mention email
|
||||
t.is(t.context.module.mails.count('Mention'), mentionMailCount);
|
||||
t.is(module.mails.count('Mention'), mentionMailCount);
|
||||
});
|
||||
|
||||
@@ -132,8 +132,12 @@ export class NotificationService {
|
||||
|
||||
async createInvitationAccepted(input: InvitationNotificationCreate) {
|
||||
const workspaceId = input.body.workspaceId;
|
||||
const userId = input.userId;
|
||||
if (!(await this.isActiveWorkspaceUser(workspaceId, userId))) {
|
||||
if (
|
||||
!(await this.isActiveWorkspaceUser(
|
||||
workspaceId,
|
||||
input.body.createdByUserId
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this.ensureWorkspaceContentExists(workspaceId);
|
||||
@@ -194,8 +198,9 @@ export class NotificationService {
|
||||
|
||||
async createInvitationReviewRequest(input: InvitationNotificationCreate) {
|
||||
const workspaceId = input.body.workspaceId;
|
||||
const userId = input.userId;
|
||||
if (await this.isActiveWorkspaceUser(workspaceId, userId)) {
|
||||
if (
|
||||
await this.isActiveWorkspaceUser(workspaceId, input.body.createdByUserId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this.ensureWorkspaceContentExists(workspaceId);
|
||||
|
||||
Reference in New Issue
Block a user