SystemWebhook側の対処

This commit is contained in:
おさむのひと 2024-11-23 11:53:09 +09:00
parent 6f64691219
commit ef61f24c33
7 changed files with 149 additions and 72 deletions

View file

@ -160,23 +160,15 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
}; };
}); });
const recipientWebhookIds = await this.fetchWebhookRecipients() return Promise.all(
.then(it => it
.filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook')
.map(it => it.systemWebhookId)
.filter(x => x != null));
for (const webhookId of recipientWebhookIds) {
await Promise.all(
convertedReports.map(it => { convertedReports.map(it => {
return this.systemWebhookService.enqueueSystemWebhook( return this.systemWebhookService.enqueueSystemWebhook(
webhookId,
type, type,
it, it,
); );
}), }),
); );
} }
}
/** /**
* . * .

View file

@ -50,7 +50,6 @@ export type SystemWebhookPayload<T extends SystemWebhookEventType> =
@Injectable() @Injectable()
export class SystemWebhookService implements OnApplicationShutdown { export class SystemWebhookService implements OnApplicationShutdown {
private logger: Logger;
private activeSystemWebhooksFetched = false; private activeSystemWebhooksFetched = false;
private activeSystemWebhooks: MiSystemWebhook[] = []; private activeSystemWebhooks: MiSystemWebhook[] = [];
@ -62,11 +61,9 @@ export class SystemWebhookService implements OnApplicationShutdown {
private idService: IdService, private idService: IdService,
private queueService: QueueService, private queueService: QueueService,
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
private loggerService: LoggerService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
) { ) {
this.redisForSub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
this.logger = this.loggerService.getLogger('webhook');
} }
@bindThis @bindThis
@ -193,28 +190,19 @@ export class SystemWebhookService implements OnApplicationShutdown {
/** /**
* SystemWebhook Webhook配送キューに追加する * SystemWebhook Webhook配送キューに追加する
* @see QueueService.systemWebhookDeliver * @see QueueService.systemWebhookDeliver
* // TODO: contentの型を厳格化する
*/ */
@bindThis @bindThis
public async enqueueSystemWebhook<T extends SystemWebhookEventType>( public async enqueueSystemWebhook<T extends SystemWebhookEventType>(
webhook: MiSystemWebhook | MiSystemWebhook['id'],
type: T, type: T,
content: SystemWebhookPayload<T>, content: SystemWebhookPayload<T>,
) { ) {
const webhookEntity = typeof webhook === 'string' const webhooks = await this.fetchActiveSystemWebhooks()
? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook) .then(webhooks => webhooks.filter(webhook => webhook.on.includes(type)));
: webhook; return Promise.all(
if (!webhookEntity || !webhookEntity.isActive) { webhooks.map(webhook => {
this.logger.info(`SystemWebhook is not active or not found : ${webhook}`); return this.queueService.systemWebhookDeliver(webhook, type, content);
return; }),
} );
if (!webhookEntity.on.includes(type)) {
this.logger.info(`SystemWebhook ${webhookEntity.id} is not listening to ${type}`);
return;
}
return this.queueService.systemWebhookDeliver(webhookEntity, type, content);
} }
@bindThis @bindThis

View file

@ -63,13 +63,6 @@ export class UserService {
@bindThis @bindThis
public async notifySystemWebhook(user: MiUser, type: 'userCreated') { public async notifySystemWebhook(user: MiUser, type: 'userCreated') {
const packedUser = await this.userEntityService.pack(user, null, { schema: 'UserLite' }); const packedUser = await this.userEntityService.pack(user, null, { schema: 'UserLite' });
const recipientWebhookIds = await this.systemWebhookService.fetchSystemWebhooks({ isActive: true, on: [type] }); return this.systemWebhookService.enqueueSystemWebhook(type, packedUser);
for (const webhookId of recipientWebhookIds) {
await this.systemWebhookService.enqueueSystemWebhook(
webhookId,
type,
packedUser,
);
}
} }
} }

View file

@ -231,16 +231,11 @@ export class CheckModeratorsActivityProcessorService {
// -- SystemWebhook // -- SystemWebhook
const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks() return this.systemWebhookService.enqueueSystemWebhook(
.then(it => it.filter(it => it.on.includes('inactiveModeratorsWarning')));
for (const systemWebhook of systemWebhooks) {
this.systemWebhookService.enqueueSystemWebhook(
systemWebhook,
'inactiveModeratorsWarning', 'inactiveModeratorsWarning',
{ remainingTime: remainingTime }, { remainingTime: remainingTime },
); );
} }
}
@bindThis @bindThis
public async notifyChangeToInvitationOnly() { public async notifyChangeToInvitationOnly() {
@ -269,16 +264,11 @@ export class CheckModeratorsActivityProcessorService {
// -- SystemWebhook // -- SystemWebhook
const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks() return this.systemWebhookService.enqueueSystemWebhook(
.then(it => it.filter(it => it.on.includes('inactiveModeratorsInvitationOnlyChanged')));
for (const systemWebhook of systemWebhooks) {
this.systemWebhookService.enqueueSystemWebhook(
systemWebhook,
'inactiveModeratorsInvitationOnlyChanged', 'inactiveModeratorsInvitationOnlyChanged',
{}, {},
); );
} }
}
@bindThis @bindThis
private async fetchModerators() { private async fetchModerators() {

View file

@ -314,9 +314,10 @@ describe('SystemWebhookService', () => {
isActive: true, isActive: true,
on: ['abuseReport'], on: ['abuseReport'],
}); });
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any); await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).toHaveBeenCalled(); expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook);
}); });
test('非アクティブなWebhookはキューに追加されない', async () => { test('非アクティブなWebhookはキューに追加されない', async () => {
@ -324,7 +325,7 @@ describe('SystemWebhookService', () => {
isActive: false, isActive: false,
on: ['abuseReport'], on: ['abuseReport'],
}); });
await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any); await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled(); expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
}); });
@ -338,11 +339,33 @@ describe('SystemWebhookService', () => {
isActive: true, isActive: true,
on: ['abuseReportResolved'], on: ['abuseReportResolved'],
}); });
await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' } as any); await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled(); expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
}); });
test('混在した時、有効かつ許可されたイベント種別のみ', async () => {
const webhook1 = await createWebhook({
isActive: true,
on: ['abuseReport'],
});
const webhook2 = await createWebhook({
isActive: true,
on: ['abuseReportResolved'],
});
const webhook3 = await createWebhook({
isActive: false,
on: ['abuseReport'],
});
const webhook4 = await createWebhook({
isActive: false,
on: ['abuseReportResolved'],
});
await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1);
});
}); });
describe('fetchActiveSystemWebhooks', () => { describe('fetchActiveSystemWebhooks', () => {

View file

@ -1,4 +1,3 @@
/* /*
* SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
@ -71,7 +70,7 @@ describe('UserWebhookService', () => {
LoggerService, LoggerService,
GlobalEventService, GlobalEventService,
{ {
provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }), provide: QueueService, useFactory: () => ({ userWebhookDeliver: jest.fn() }),
}, },
], ],
}) })
@ -242,4 +241,92 @@ describe('UserWebhookService', () => {
}); });
}); });
}); });
describe('アプリを毎回作り直す必要があるグループ', () => {
beforeEach(async () => {
await beforeAllImpl();
await beforeEachImpl();
});
afterEach(async () => {
await afterEachImpl();
await afterAllImpl();
});
describe('enqueueUserWebhook', () => {
test('キューに追加成功', async () => {
const webhook = await createWebhook({
active: true,
on: ['note'],
});
await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any);
expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1);
expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook);
});
test('非アクティブなWebhookはキューに追加されない', async () => {
const webhook = await createWebhook({
active: false,
on: ['note'],
});
await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any);
expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
});
test('未許可のイベント種別が渡された場合はWebhookはキューに追加されない', async () => {
const webhook1 = await createWebhook({
active: true,
on: [],
});
const webhook2 = await createWebhook({
active: true,
on: ['note'],
});
await service.enqueueUserWebhook(webhook1.userId, 'renote', { foo: 'bar' } as any);
await service.enqueueUserWebhook(webhook2.userId, 'renote', { foo: 'bar' } as any);
expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
});
test('ユーザIDが異なるWebhookはキューに追加されない', async () => {
const webhook = await createWebhook({
active: true,
on: ['note'],
});
await service.enqueueUserWebhook(idService.gen(), 'note', { foo: 'bar' } as any);
expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
});
test('混在した時、有効かつ許可されたイベント種別のみ', async () => {
const userId = root.id;
const webhook1 = await createWebhook({
userId,
active: true,
on: ['note'],
});
const webhook2 = await createWebhook({
userId,
active: true,
on: ['renote'],
});
const webhook3 = await createWebhook({
userId,
active: false,
on: ['note'],
});
const webhook4 = await createWebhook({
userId,
active: false,
on: ['renote'],
});
await service.enqueueUserWebhook(userId, 'note', { foo: 'bar' } as any);
expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1);
expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook1);
});
});
});
}); });

View file

@ -18,6 +18,7 @@ import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
import { EmailService } from '@/core/EmailService.js'; import { EmailService } from '@/core/EmailService.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { AnnouncementService } from '@/core/AnnouncementService.js'; import { AnnouncementService } from '@/core/AnnouncementService.js';
import { SystemWebhookEventType } from '@/models/SystemWebhook.js';
const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0)); const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0));
@ -334,9 +335,10 @@ describe('CheckModeratorsActivityProcessorService', () => {
mockModeratorRole([user1]); mockModeratorRole([user1]);
await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 }); await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 });
expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(2); // typeとactiveによる絞り込みが機能しているかはSystemWebhookServiceのテストで確認する.
expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook1); // ここでは呼び出されているか、typeが正しいかのみを確認する
expect(systemWebhookService.enqueueSystemWebhook.mock.calls[1][0]).toEqual(systemWebhook2); expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsWarning');
}); });
}); });
@ -372,8 +374,10 @@ describe('CheckModeratorsActivityProcessorService', () => {
mockModeratorRole([user1]); mockModeratorRole([user1]);
await service.notifyChangeToInvitationOnly(); await service.notifyChangeToInvitationOnly();
// typeとactiveによる絞り込みが機能しているかはSystemWebhookServiceのテストで確認する.
// ここでは呼び出されているか、typeが正しいかのみを確認する
expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1); expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook2); expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsInvitationOnlyChanged');
}); });
}); });
}); });