mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
9c26535815
* Auto-subscribe mentioned users to documnet * Add tests for mention auto-subscribe and a buildMention factory * Add tests that prior unsubscribes are respected when mentioned Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Batch mention subscriptions into a single transaction Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
811 lines
23 KiB
TypeScript
811 lines
23 KiB
TypeScript
import {
|
||
MentionType,
|
||
NotificationEventType,
|
||
SubscriptionType,
|
||
} from "@shared/types";
|
||
import { createContext } from "@server/context";
|
||
import { parser } from "@server/editor";
|
||
import type { Document } from "@server/models";
|
||
import {
|
||
View,
|
||
Subscription,
|
||
Event,
|
||
Notification,
|
||
Revision,
|
||
} from "@server/models";
|
||
import {
|
||
buildDocument,
|
||
buildGroup,
|
||
buildGroupUser,
|
||
buildMention,
|
||
buildProseMirrorDoc,
|
||
buildUser,
|
||
} from "@server/test/factories";
|
||
import RevisionCreatedNotificationsTask from "./RevisionCreatedNotificationsTask";
|
||
|
||
const ip = "127.0.0.1";
|
||
|
||
beforeEach(async () => {
|
||
vi.resetAllMocks();
|
||
});
|
||
|
||
function updateDocumentText(document: Document, text: string) {
|
||
document.content = parser.parse(text)?.toJSON();
|
||
document.updatedAt = new Date();
|
||
return document;
|
||
}
|
||
|
||
describe("revisions.create", () => {
|
||
test("should send a notification to other collaborators", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const user = await buildUser();
|
||
let document = await buildDocument({
|
||
teamId: user.teamId,
|
||
userId: user.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user }), document);
|
||
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const collaborator = await buildUser({ teamId: document.teamId });
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
document.collaboratorIds = [user.id, collaborator.id];
|
||
await document.save();
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
expect(spy).toHaveBeenCalled();
|
||
});
|
||
|
||
test("should not send a notification if viewed since update", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const user = await buildUser();
|
||
let document = await buildDocument({
|
||
teamId: user.teamId,
|
||
userId: user.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user }), document);
|
||
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const collaborator = await buildUser({ teamId: document.teamId });
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
document.collaboratorIds = [user.id, collaborator.id];
|
||
await document.save();
|
||
|
||
await View.create({
|
||
userId: collaborator.id,
|
||
documentId: document.id,
|
||
});
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
expect(spy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test("should not send a notification to last editor", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const user = await buildUser();
|
||
let document = await buildDocument({
|
||
teamId: user.teamId,
|
||
lastModifiedById: user.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user }), document);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user }),
|
||
document
|
||
);
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: user.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
expect(spy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test("should send a notification for subscriptions, even to collaborator", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const user = await buildUser();
|
||
let document = await buildDocument({
|
||
teamId: user.teamId,
|
||
userId: user.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user }), document);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user }),
|
||
document
|
||
);
|
||
const collaborator = await buildUser({ teamId: document.teamId });
|
||
const subscriber = await buildUser({ teamId: document.teamId });
|
||
|
||
document.collaboratorIds = [collaborator.id, subscriber.id];
|
||
|
||
await document.save();
|
||
|
||
await Subscription.create({
|
||
userId: subscriber.id,
|
||
documentId: document.id,
|
||
event: "documents.update",
|
||
});
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
expect(spy).toHaveBeenCalled();
|
||
});
|
||
|
||
test("should create subscriptions for collaborator", async () => {
|
||
const collaborator0 = await buildUser();
|
||
const collaborator1 = await buildUser({ teamId: collaborator0.teamId });
|
||
const collaborator2 = await buildUser({ teamId: collaborator0.teamId });
|
||
let document = await buildDocument({
|
||
teamId: collaborator0.teamId,
|
||
userId: collaborator0.id,
|
||
});
|
||
await Revision.createFromDocument(
|
||
createContext({ user: collaborator0 }),
|
||
document
|
||
);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator0 }),
|
||
document
|
||
);
|
||
|
||
await document.update({
|
||
collaboratorIds: [collaborator0.id, collaborator1.id, collaborator2.id],
|
||
});
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator0.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
const events = await Event.findAll({
|
||
where: {
|
||
name: "subscriptions.create",
|
||
teamId: document.teamId,
|
||
},
|
||
});
|
||
|
||
// Should emit 3 `subscriptions.create` events.
|
||
expect(events.length).toEqual(3);
|
||
|
||
// Each event should point to same document.
|
||
expect(events.every((event) => event.documentId === document.id)).toEqual(
|
||
true
|
||
);
|
||
|
||
// Events should mention correct `userId`.
|
||
const userIds = events.map((event) => event.userId);
|
||
expect(userIds).toEqual(
|
||
expect.arrayContaining([
|
||
collaborator0.id,
|
||
collaborator1.id,
|
||
collaborator2.id,
|
||
])
|
||
);
|
||
expect(userIds.length).toBe(3);
|
||
});
|
||
|
||
test("should not send multiple emails", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const collaborator0 = await buildUser();
|
||
const collaborator1 = await buildUser({ teamId: collaborator0.teamId });
|
||
const collaborator2 = await buildUser({ teamId: collaborator0.teamId });
|
||
let document = await buildDocument({
|
||
teamId: collaborator0.teamId,
|
||
userId: collaborator0.id,
|
||
});
|
||
await Revision.createFromDocument(
|
||
createContext({ user: collaborator0 }),
|
||
document
|
||
);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator0 }),
|
||
document
|
||
);
|
||
|
||
await document.update({
|
||
collaboratorIds: [collaborator0.id, collaborator1.id, collaborator2.id],
|
||
});
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
// Those changes will also emit a `revisions.create` event.
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator0.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
// This should send out 2 emails, one for each collaborator that did not
|
||
// participate in the edit
|
||
expect(spy).toHaveBeenCalledTimes(2);
|
||
});
|
||
|
||
test("should not create subscriptions if previously unsubscribed", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const collaborator0 = await buildUser();
|
||
const collaborator1 = await buildUser({ teamId: collaborator0.teamId });
|
||
const collaborator2 = await buildUser({ teamId: collaborator0.teamId });
|
||
let document = await buildDocument({
|
||
teamId: collaborator0.teamId,
|
||
userId: collaborator0.id,
|
||
});
|
||
await Revision.createFromDocument(
|
||
createContext({ user: collaborator0 }),
|
||
document
|
||
);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator0 }),
|
||
document
|
||
);
|
||
|
||
await document.update({
|
||
collaboratorIds: [collaborator0.id, collaborator1.id, collaborator2.id],
|
||
});
|
||
|
||
// `collaborator2` created a subscription.
|
||
const subscription2 = await Subscription.create({
|
||
userId: collaborator2.id,
|
||
documentId: document.id,
|
||
event: "documents.update",
|
||
});
|
||
|
||
// `collaborator2` would no longer like to be notified.
|
||
await subscription2.destroy();
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator0.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
const events = await Event.findAll({
|
||
where: {
|
||
name: "subscriptions.create",
|
||
teamId: document.teamId,
|
||
},
|
||
});
|
||
|
||
// Should emit 2 `subscriptions.create` events.
|
||
expect(events.length).toEqual(2);
|
||
expect(events.every((event) => event.documentId === document.id)).toEqual(
|
||
true
|
||
);
|
||
expect(events.some((event) => event.userId === collaborator0.id)).toEqual(
|
||
true
|
||
);
|
||
expect(events.some((event) => event.userId === collaborator1.id)).toEqual(
|
||
true
|
||
);
|
||
|
||
// One notification as one collaborator performed edit and the other is
|
||
// unsubscribed
|
||
expect(spy).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
test("should send a notification for subscriptions to non-collaborators", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
let document = await buildDocument();
|
||
const collaborator = await buildUser({ teamId: document.teamId });
|
||
const subscriber = await buildUser({ teamId: document.teamId });
|
||
await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
|
||
// `subscriber` hasn't collaborated on `document`.
|
||
document.collaboratorIds = [collaborator.id];
|
||
|
||
await document.save();
|
||
|
||
// `subscriber` subscribes to `document`'s changes.
|
||
// Specifically "documents.update" event.
|
||
await Subscription.create({
|
||
userId: subscriber.id,
|
||
documentId: document.id,
|
||
event: "documents.update",
|
||
});
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
expect(spy).toHaveBeenCalled();
|
||
});
|
||
|
||
test("should not send a notification for subscriptions to collaborators if unsubscribed", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
|
||
let document = await buildDocument();
|
||
const collaborator = await buildUser({ teamId: document.teamId });
|
||
const subscriber = await buildUser({ teamId: document.teamId });
|
||
await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
|
||
// `subscriber` has collaborated on `document`.
|
||
document.collaboratorIds = [collaborator.id, subscriber.id];
|
||
|
||
await document.save();
|
||
|
||
// `subscriber` subscribes to `document`'s changes.
|
||
// Specifically "documents.update" event.
|
||
const subscription = await Subscription.create({
|
||
userId: subscriber.id,
|
||
documentId: document.id,
|
||
event: "documents.update",
|
||
});
|
||
|
||
await subscription.destroy();
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
// Should send notification to `collaborator` and not `subscriber`.
|
||
expect(spy).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
test("should not send a notification for subscriptions to members outside of the team", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
|
||
let document = await buildDocument();
|
||
const collaborator = await buildUser({ teamId: document.teamId });
|
||
await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
document = updateDocumentText(document, "Updated body content");
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
|
||
// `subscriber` *does not* belong
|
||
// to `collaborator`'s team,
|
||
const subscriber = await buildUser();
|
||
|
||
// `subscriber` hasn't collaborated on `document`.
|
||
document.collaboratorIds = [collaborator.id];
|
||
|
||
await document.save();
|
||
|
||
// `subscriber` subscribes to `document`'s changes.
|
||
// Specifically "documents.update" event.
|
||
// Not sure how they got hold of this document,
|
||
// but let's just pretend they did!
|
||
await Subscription.create({
|
||
userId: subscriber.id,
|
||
documentId: document.id,
|
||
event: "documents.update",
|
||
});
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
// Should send notification to `collaborator` and not `subscriber`.
|
||
expect(spy).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
test("should not send a notification if viewed since update", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
|
||
const document = await buildDocument();
|
||
const collaborator = await buildUser({ teamId: document.teamId });
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: collaborator }),
|
||
document
|
||
);
|
||
document.collaboratorIds = [collaborator.id];
|
||
await document.save();
|
||
|
||
await View.create({
|
||
userId: collaborator.id,
|
||
documentId: document.id,
|
||
});
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: collaborator.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
expect(spy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test("should not send a notification to last editor", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
|
||
const user = await buildUser();
|
||
const document = await buildDocument({
|
||
teamId: user.teamId,
|
||
lastModifiedById: user.id,
|
||
});
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user }),
|
||
document
|
||
);
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: user.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
expect(spy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test("should send a mention notification even when change is below threshold", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const actor = await buildUser();
|
||
const mentioned = await buildUser({ teamId: actor.teamId, name: "Kim" });
|
||
|
||
// Build a document with some initial content
|
||
let document = await buildDocument({
|
||
teamId: actor.teamId,
|
||
userId: actor.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user: actor }), document);
|
||
|
||
// Now add a mention – the only change is the mention node itself, which
|
||
// renders as "@<label>" in plain text and may be below the 5-char
|
||
// threshold that gates generic update notifications.
|
||
document.content = buildProseMirrorDoc([
|
||
...(document.content?.content ?? []),
|
||
{
|
||
type: "paragraph",
|
||
content: [
|
||
buildMention({
|
||
id: "test-mention-id",
|
||
modelId: mentioned.id,
|
||
actorId: actor.id,
|
||
label: mentioned.name,
|
||
}),
|
||
],
|
||
},
|
||
]).toJSON();
|
||
document.updatedAt = new Date();
|
||
await document.save();
|
||
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: actor }),
|
||
document
|
||
);
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: actor.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
expect(spy).toHaveBeenCalledWith(
|
||
expect.objectContaining({
|
||
event: NotificationEventType.MentionedInDocument,
|
||
userId: mentioned.id,
|
||
actorId: actor.id,
|
||
documentId: document.id,
|
||
})
|
||
);
|
||
});
|
||
|
||
test("should not send a notification for group mentions when disableMentions is true", async () => {
|
||
const spy = vi.spyOn(Notification, "create");
|
||
const actor = await buildUser();
|
||
const group = await buildGroup({
|
||
teamId: actor.teamId,
|
||
disableMentions: true,
|
||
});
|
||
const member = await buildUser({ teamId: actor.teamId });
|
||
await buildGroupUser({ groupId: group.id, userId: member.id });
|
||
|
||
member.setNotificationEventType(
|
||
NotificationEventType.GroupMentionedInDocument
|
||
);
|
||
await member.save();
|
||
|
||
let document = await buildDocument({
|
||
teamId: actor.teamId,
|
||
userId: actor.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user: actor }), document);
|
||
|
||
// Update document to include a group mention
|
||
document.content = buildProseMirrorDoc([
|
||
{
|
||
type: "paragraph",
|
||
content: [
|
||
{
|
||
type: "text",
|
||
text: "Updated content with a group mention ",
|
||
},
|
||
buildMention({
|
||
type: MentionType.Group,
|
||
modelId: group.id,
|
||
actorId: actor.id,
|
||
label: group.name,
|
||
}),
|
||
],
|
||
},
|
||
]).toJSON();
|
||
document.updatedAt = new Date();
|
||
await document.save();
|
||
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: actor }),
|
||
document
|
||
);
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: actor.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
expect(spy).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it("should subscribe a mentioned user to the document", async () => {
|
||
const actor = await buildUser();
|
||
const mentioned = await buildUser({ teamId: actor.teamId, name: "Kim" });
|
||
|
||
const document = await buildDocument({
|
||
teamId: actor.teamId,
|
||
userId: actor.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user: actor }), document);
|
||
|
||
document.content = buildProseMirrorDoc([
|
||
...(document.content?.content ?? []),
|
||
{
|
||
type: "paragraph",
|
||
content: [buildMention({ modelId: mentioned.id, actorId: actor.id })],
|
||
},
|
||
]).toJSON();
|
||
document.updatedAt = new Date();
|
||
await document.save();
|
||
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: actor }),
|
||
document
|
||
);
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: actor.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
const subscription = await Subscription.findOne({
|
||
where: {
|
||
userId: mentioned.id,
|
||
documentId: document.id,
|
||
event: SubscriptionType.Document,
|
||
},
|
||
});
|
||
expect(subscription).not.toBeNull();
|
||
});
|
||
|
||
it("should respect a prior unsubscribe when a user is mentioned", async () => {
|
||
const actor = await buildUser();
|
||
const mentioned = await buildUser({ teamId: actor.teamId });
|
||
|
||
const document = await buildDocument({
|
||
teamId: actor.teamId,
|
||
userId: actor.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user: actor }), document);
|
||
|
||
// The mentioned user previously subscribed and then unsubscribed.
|
||
const prior = await Subscription.create({
|
||
userId: mentioned.id,
|
||
documentId: document.id,
|
||
event: SubscriptionType.Document,
|
||
});
|
||
await prior.destroy();
|
||
|
||
document.content = buildProseMirrorDoc([
|
||
...(document.content?.content ?? []),
|
||
{
|
||
type: "paragraph",
|
||
content: [buildMention({ modelId: mentioned.id, actorId: actor.id })],
|
||
},
|
||
]).toJSON();
|
||
document.updatedAt = new Date();
|
||
await document.save();
|
||
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: actor }),
|
||
document
|
||
);
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: actor.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
// No active subscription should exist.
|
||
const active = await Subscription.findOne({
|
||
where: {
|
||
userId: mentioned.id,
|
||
documentId: document.id,
|
||
event: SubscriptionType.Document,
|
||
},
|
||
});
|
||
expect(active).toBeNull();
|
||
|
||
// The original soft-deleted subscription should still be soft-deleted.
|
||
const withDeleted = await Subscription.findOne({
|
||
where: {
|
||
userId: mentioned.id,
|
||
documentId: document.id,
|
||
event: SubscriptionType.Document,
|
||
},
|
||
paranoid: false,
|
||
});
|
||
expect(withDeleted).not.toBeNull();
|
||
expect(withDeleted?.deletedAt).not.toBeNull();
|
||
});
|
||
|
||
it("should not subscribe users mentioned via a group", async () => {
|
||
const actor = await buildUser();
|
||
const group = await buildGroup({ teamId: actor.teamId });
|
||
const member = await buildUser({ teamId: actor.teamId });
|
||
await buildGroupUser({ groupId: group.id, userId: member.id });
|
||
|
||
const document = await buildDocument({
|
||
teamId: actor.teamId,
|
||
userId: actor.id,
|
||
});
|
||
await Revision.createFromDocument(createContext({ user: actor }), document);
|
||
|
||
document.content = buildProseMirrorDoc([
|
||
{
|
||
type: "paragraph",
|
||
content: [
|
||
buildMention({
|
||
type: MentionType.Group,
|
||
modelId: group.id,
|
||
actorId: actor.id,
|
||
label: group.name,
|
||
}),
|
||
],
|
||
},
|
||
]).toJSON();
|
||
document.updatedAt = new Date();
|
||
await document.save();
|
||
|
||
const revision = await Revision.createFromDocument(
|
||
createContext({ user: actor }),
|
||
document
|
||
);
|
||
|
||
const task = new RevisionCreatedNotificationsTask();
|
||
await task.perform({
|
||
name: "revisions.create",
|
||
documentId: document.id,
|
||
teamId: document.teamId,
|
||
actorId: actor.id,
|
||
modelId: revision.id,
|
||
ip,
|
||
});
|
||
|
||
const subscription = await Subscription.findOne({
|
||
where: {
|
||
userId: member.id,
|
||
documentId: document.id,
|
||
event: SubscriptionType.Document,
|
||
},
|
||
});
|
||
expect(subscription).toBeNull();
|
||
});
|
||
});
|