Skip to content

Commit

Permalink
fix(backend): Webhook Test一致性+リアクションhook実装
Browse files Browse the repository at this point in the history
Signed-off-by: eternal-flame-AD <[email protected]>
  • Loading branch information
eternal-flame-AD committed Oct 29, 2024
1 parent a96f09c commit 4bc97c7
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 17 deletions.
3 changes: 2 additions & 1 deletion packages/backend/src/core/QueueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type {
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
import type { Packed } from '@/misc/json-schema.js';

@Injectable()
export class QueueService {
Expand Down Expand Up @@ -471,7 +472,7 @@ export class QueueService {
public userWebhookDeliver(
webhook: MiWebhook,
type: typeof webhookEventTypes[number],
content: unknown,
content: Packed<'UserWebhookBody'>,
opts?: { attempts?: number },
) {
const data: UserWebhookDeliverJobData = {
Expand Down
29 changes: 28 additions & 1 deletion packages/backend/src/core/ReactionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
import { UserWebhookService } from './UserWebhookService.js';
import { QueueService } from './QueueService.js';
import { Packed } from '@/misc/json-schema.js';

const FALLBACK = '\u2764';

Expand Down Expand Up @@ -94,6 +97,8 @@ export class ReactionService {
private reactionsBufferingService: ReactionsBufferingService,
private idService: IdService,
private featuredService: FeaturedService,
private queueService: QueueService,
private webhookService: UserWebhookService,
private globalEventService: GlobalEventService,
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
Expand Down Expand Up @@ -254,12 +259,34 @@ export class ReactionService {
userId: user.id,
});

// リアクションされたユーザーがローカルユーザーなら通知を作成
// リアクションされたユーザーがローカルユーザーなら通知を作成してWebhookを送信
if (note.userHost === null) {
this.notificationService.createNotification(note.userId, 'reaction', {
noteId: note.id,
reaction: reaction,
}, user.id);


Check failure on line 269 in packages/backend/src/core/ReactionService.ts

View workflow job for this annotation

GitHub Actions / lint (backend)

More than 1 blank line not allowed
this.webhookService.getActiveWebhooks().then(async webhooks => {
webhooks = webhooks.filter(x => x.userId === note.userId && x.on.includes('reaction'));
if (webhooks.length === 0) return;

const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
const userObj = await this.userEntityService.pack(user.id, null, { schema: 'UserLite' });

const payload: Packed<'UserWebhookReactionBody'> = {
note: noteObj,
reaction: {
id: record.id,
user: userObj,
reaction: reaction,
},
};

for (const webhook of webhooks) {
this.queueService.userWebhookDeliver(webhook, 'reaction', payload);
}
})

Check failure on line 289 in packages/backend/src/core/ReactionService.ts

View workflow job for this annotation

GitHub Actions / lint (backend)

Missing semicolon
}

//#region 配信
Expand Down
47 changes: 39 additions & 8 deletions packages/backend/src/core/WebhookTestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailed
};
}

function generateDummyReactionPayload(note_override?: Partial<MiNote>): Packed<'UserWebhookReactionBody'> {
const dummyNote = generateDummyNote(note_override);
const dummyReaction = {
id: 'dummy-reaction-1',
user: toPackedUserLite(generateDummyUser()),
reaction: 'test_reaction',
};

return {
note: toPackedNote(dummyNote),
reaction: dummyReaction,
};
}

const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({
id: 'dummy-user-2',
Expand All @@ -285,6 +299,10 @@ const dummyUser3 = generateDummyUser({
notesCount: 15900,
});

function wrapBodyEnum<T extends string, U>(tag: T, body: U): { [K in T]: U } {
return { [tag]: body } as { [K in T]: U };
}

@Injectable()
export class WebhookTestService {
public static NoSuchWebhookError = class extends Error {
Expand Down Expand Up @@ -321,7 +339,11 @@ export class WebhookTestService {
}

const webhook = webhooks[0];
const send = (contents: unknown) => {
const send = (contents:
Packed<'UserWebhookNoteBody'> |
Packed<'UserWebhookUserBody'> |
Packed<'UserWebhookReactionBody'>,
) => {
const merged = {
...webhook,
...params.override,
Expand Down Expand Up @@ -361,33 +383,42 @@ export class WebhookTestService {

switch (params.type) {
case 'note': {
send(toPackedNote(dummyNote1));
send(wrapBodyEnum('note', toPackedNote(dummyNote1)));
break;
}
case 'reply': {
send(toPackedNote(dummyReply1));
send(wrapBodyEnum('note', toPackedNote(dummyReply1)));
break;
}
case 'renote': {
send(toPackedNote(dummyRenote1));
send(wrapBodyEnum('note', toPackedNote(dummyRenote1)));
break;
}
case 'mention': {
send(toPackedNote(dummyMention1));
send(wrapBodyEnum('note', toPackedNote(dummyMention1)));
break;
}
case 'follow': {
send(toPackedUserDetailedNotMe(dummyUser1));
send(wrapBodyEnum('user', toPackedUserDetailedNotMe(dummyUser2)));
break;
}
case 'followed': {
send(toPackedUserLite(dummyUser2));
send(wrapBodyEnum('user', toPackedUserDetailedNotMe(dummyUser2)));
break;
}
case 'unfollow': {
send(toPackedUserDetailedNotMe(dummyUser3));
send(wrapBodyEnum('user', toPackedUserDetailedNotMe(dummyUser2)));
break;
}
case 'reaction': {
send(generateDummyReactionPayload());
break;
}
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _exhaustiveAssertion: never = params.type;
return;
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/misc/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
} from '@/models/json-schema/meta.js';
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
import { packedUserWebhookBodySchema, packedUserWebhookNoteBodySchema, packedUserWebhookReactionBodySchema, packedUserWebhookUserBodySchema } from '@/models/json-schema/user-webhook.js';

export const refs = {
UserLite: packedUserLiteSchema,
Expand All @@ -68,6 +69,10 @@ export const refs = {
MeDetailed: packedMeDetailedSchema,
UserDetailed: packedUserDetailedSchema,
User: packedUserSchema,
UserWebhookBody: packedUserWebhookBodySchema,
UserWebhookNoteBody: packedUserWebhookNoteBodySchema,
UserWebhookUserBody: packedUserWebhookUserBodySchema,
UserWebhookReactionBody: packedUserWebhookReactionBodySchema,

UserList: packedUserListSchema,
Ad: packedAdSchema,
Expand Down
80 changes: 80 additions & 0 deletions packages/backend/src/models/json-schema/user-webhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export const packedUserWebhookNoteBodySchema = {
type: 'object',
properties: {
note: {
type: 'object',
ref: 'Note',
optional: false,
nullable: false,
},
},
nullable: false,
optional: false,
} as const;

export const packedUserWebhookUserBodySchema = {
type: 'object',
properties: {
user: {
type: 'object',
ref: 'UserDetailedNotMe',
optional: false,
nullable: false,
},
},
nullable: false,
optional: false,
} as const;

export const packedUserWebhookReactionBodySchema = {
type: 'object',
properties: {
note: {
type: 'object',
ref: 'Note',
optional: false,
nullable: false,
},
reaction: {
type: 'object',
properties: {
id: {
type: 'string',
optional: false,
nullable: false,
},
user: {
type: 'object',
ref: 'UserLite',
optional: false,
nullable: false,
},
reaction: {
type: 'string',
optional: false,
nullable: false,
},
},
optional: false,
nullable: false,
},
},
nullable: false,
optional: false,
} as const;

export const packedUserWebhookBodySchema = {
type: 'object',
oneOf: [
packedUserWebhookNoteBodySchema,
packedUserWebhookUserBodySchema,
packedUserWebhookReactionBodySchema,
],
nullable: false,
optional: false,
} as const;
24 changes: 17 additions & 7 deletions packages/backend/test/unit/WebhookTestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersReposi
import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
import { QueueService } from '@/core/QueueService.js';
import { Packed } from '@/misc/json-schema.js';

describe('WebhookTestService', () => {
let app: TestingModule;
Expand Down Expand Up @@ -122,7 +123,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('note');
expect((calls[2] as any).id).toBe('dummy-note-1');
expect((calls[2] as Packed<'UserWebhookNoteBody'>).note.id).toBe('dummy-note-1');
});

test('reply', async () => {
Expand All @@ -131,7 +132,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('reply');
expect((calls[2] as any).id).toBe('dummy-reply-1');
expect((calls[2] as Packed<'UserWebhookNoteBody'>).note.id).toBe('dummy-reply-1');
});

test('renote', async () => {
Expand All @@ -140,7 +141,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('renote');
expect((calls[2] as any).id).toBe('dummy-renote-1');
expect((calls[2] as Packed<'UserWebhookNoteBody'>).note.id).toBe('dummy-renote-1');
});

test('mention', async () => {
Expand All @@ -149,7 +150,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('mention');
expect((calls[2] as any).id).toBe('dummy-mention-1');
expect((calls[2] as Packed<'UserWebhookNoteBody'>).note.id).toBe('dummy-mention-1');
});

test('follow', async () => {
Expand All @@ -158,7 +159,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('follow');
expect((calls[2] as any).id).toBe('dummy-user-1');
expect((calls[2] as Packed<'UserWebhookUserBody'>).user.id).toBe('dummy-user-2');
});

test('followed', async () => {
Expand All @@ -167,7 +168,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('followed');
expect((calls[2] as any).id).toBe('dummy-user-2');
expect((calls[2] as Packed<'UserWebhookUserBody'>).user.id).toBe('dummy-user-2');
});

test('unfollow', async () => {
Expand All @@ -176,7 +177,16 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('unfollow');
expect((calls[2] as any).id).toBe('dummy-user-3');
expect((calls[2] as Packed<'UserWebhookUserBody'>).user.id).toBe('dummy-user-2');
});

test('reaction', async () => {
await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'reaction' }, alice);

const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('reaction');
expect((calls[2] as Packed<'UserWebhookReactionBody'>).reaction.id).toBe('dummy-reaction-1');
});

describe('NoSuchWebhookError', () => {
Expand Down

0 comments on commit 4bc97c7

Please sign in to comment.