diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts index 69115e6c1e..9bca795479 100644 --- a/packages/backend/src/core/AbuseReportNotificationService.ts +++ b/packages/backend/src/core/AbuseReportNotificationService.ts @@ -160,11 +160,19 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { }; }); + const inactiveRecipients = await this.fetchWebhookRecipients() + .then(it => it.filter(it => !it.isActive)); + const withoutWebhookIds = inactiveRecipients + .map(it => it.systemWebhookId) + .filter(x => x != null); return Promise.all( convertedReports.map(it => { return this.systemWebhookService.enqueueSystemWebhook( type, it, + { + excludes: withoutWebhookIds, + }, ); }), ); diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts index bc383c1f68..8239490adc 100644 --- a/packages/backend/src/core/SystemWebhookService.ts +++ b/packages/backend/src/core/SystemWebhookService.ts @@ -195,9 +195,14 @@ export class SystemWebhookService implements OnApplicationShutdown { public async enqueueSystemWebhook( type: T, content: SystemWebhookPayload, + opts?: { + excludes?: MiSystemWebhook['id'][]; + }, ) { const webhooks = await this.fetchActiveSystemWebhooks() - .then(webhooks => webhooks.filter(webhook => webhook.on.includes(type))); + .then(webhooks => { + return webhooks.filter(webhook => !opts?.excludes?.includes(webhook.id) && webhook.on.includes(type)); + }); return Promise.all( webhooks.map(webhook => { return this.queueService.systemWebhookDeliver(webhook, type, content); diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts index 235af29f0d..1326003c5e 100644 --- a/packages/backend/test/unit/AbuseReportNotificationService.ts +++ b/packages/backend/test/unit/AbuseReportNotificationService.ts @@ -3,13 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { jest } from '@jest/globals'; +import { describe, jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; import { randomString } from '../utils.js'; import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js'; import { AbuseReportNotificationRecipientRepository, MiAbuseReportNotificationRecipient, + MiAbuseUserReport, MiSystemWebhook, MiUser, SystemWebhooksRepository, @@ -112,7 +113,10 @@ describe('AbuseReportNotificationService', () => { provide: SystemWebhookService, useFactory: () => ({ enqueueSystemWebhook: jest.fn() }), }, { - provide: UserEntityService, useFactory: () => ({ pack: (v: any) => v }), + provide: UserEntityService, useFactory: () => ({ + pack: (v: any) => Promise.resolve(v), + packMany: (v: any) => Promise.resolve(v), + }), }, { provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }), @@ -344,4 +348,46 @@ describe('AbuseReportNotificationService', () => { expect(recipients).toEqual([recipient3]); }); }); + + describe('notifySystemWebhook', () => { + test('非アクティブな通報通知はWebhook送信から除外される', async () => { + const recipient1 = await createRecipient({ + method: 'webhook', + systemWebhookId: systemWebhook1.id, + isActive: true, + }); + const recipient2 = await createRecipient({ + method: 'webhook', + systemWebhookId: systemWebhook2.id, + isActive: false, + }); + + const reports: MiAbuseUserReport[] = [ + { + id: idService.gen(), + targetUserId: alice.id, + targetUser: alice, + reporterId: bob.id, + reporter: bob, + assigneeId: null, + assignee: null, + resolved: false, + forwarded: false, + comment: 'test', + moderationNote: '', + resolvedAs: null, + targetUserHost: null, + reporterHost: null, + }, + ]; + + await service.notifySystemWebhook(reports, 'abuseReport'); + + // 実際に除外されるかはSystemWebhookService側で確認する. + // ここでは非アクティブな通報通知を除外設定できているかを確認する + expect(webhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1); + expect(webhookService.enqueueSystemWebhook.mock.calls[0][0]).toBe('abuseReport'); + expect(webhookService.enqueueSystemWebhook.mock.calls[0][2]).toEqual({ excludes: [systemWebhook2.id] }); + }); + }); }); diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts index 9488e18f38..fee4acb305 100644 --- a/packages/backend/test/unit/SystemWebhookService.ts +++ b/packages/backend/test/unit/SystemWebhookService.ts @@ -366,6 +366,22 @@ describe('SystemWebhookService', () => { expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1); expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1); }); + + test('除外指定した場合は送信されない', async () => { + const webhook1 = await createWebhook({ + isActive: true, + on: ['abuseReport'], + }); + const webhook2 = await createWebhook({ + isActive: true, + on: ['abuseReport'], + }); + + await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any, { excludes: [webhook2.id] }); + + expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1); + expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1); + }); }); describe('fetchActiveSystemWebhooks', () => {